1. 程式人生 > >淺談整體二分

淺談整體二分

思路有些像CDQ,都是離線

整體二分滿足以下性質: 
1. 詢問的答案具有可二分性 
2. 修改對判定答案的貢獻相對獨立,修改之間互不影響效果 
3. 修改如果對判定答案有貢獻,則貢獻為一確定的與判定標準無關的值 
4. 貢獻滿足交換律,結合律,具有可加性 
5. 題目允許離線操作

時間複雜度方面還要滿足整體二分內部不能與序列的總長度相關,而要與當前的序列的長度相關

————————————————————————————————————————————————————————

例1:靜態區間第K小

二分了答案,標記0/1後,(0表示小於等於mid,1表示大於mid),字首和一下,然後再判斷分解,繼續遞迴

 

但是字首和不能與序列總長度有關

然而可以發現一個性質:原來比之前[l,r]中的mid大的,肯定比[l,mid]中的mid大,所以只用在原來的基礎上修改沒一個數就好了

所以果斷上個樹狀陣列

#include<cstdio>
#include<algorithm>
using namespace std;

const int N=1e6+5;
int n,m,ql[N],qr[N],qk[N],id[N],p,n1,n2,c[N],ans[N],t1[N],t2[N];
struct A{int id,x; }a[N];
bool cmp(A i,A j){ return i.x<j.x; }

inline void add(int x,int val)
{
	for(int i=x;i<=n;i+=i&-i) c[i]+=val;
}

inline int sum(int x)
{
	int ret=0; 
	for(int i=x;i;i-=i&-i) ret+=c[i];
	return ret;
}

void work(int l,int r,int ll,int rr)
{
	if(l>r) return;
	int mid=ll+rr>>1;
	while(p+1<=n&&a[p+1].x<=mid) p++,add(a[p].id,1);
	while(p&&a[p].x>mid) add(a[p].id,-1),p--;
	n1=n2=l;
	for(int i=l;i<=r;i++)
		if(sum(qr[id[i]])-sum(ql[id[i]]-1)>=qk[id[i]]) 
			ans[id[i]]=mid,t1[n1++]=id[i];
		else t2[n2++]=id[i];
	for(int i=l;i<n1;i++) id[i]=t1[i];
	for(int i=n1;i<=r;i++) id[i]=t2[i-n1+l];
	if(ll==rr) return;
	work(l,n1-1,ll,mid),work(n1,r,mid+1,rr);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) 
		scanf("%d",&a[i].x),a[i].id=i;
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=m;i++) 
		scanf("%d%d%d",&ql[i],&qr[i],&qk[i]),id[i]=i;
	work(1,n,a[1].x,a[n].x);
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
 } 

例2:動態區間第K小

還沒想清楚,留個坑