CDQ分治


CDQ分治

CDQ分治:用於解決離線或不強制在線問題中簡化一層樹結構的實用性分治算法

其實可以這樣說,如果CDQ分治的題空間開大一點,基本樹套樹都可以搞定,但是樹套樹的空間復雜度是O(nlog~nlog^2n),而CDQ分治一般是O(n)級別的,因此,有些CDQ分治的題目並不能用樹套樹解決。並且,一般來說寫一個CDQ分治都要比普通的樹套樹要容易的多,除了主席樹+樹狀數組...

CDQ分治主要思想還是分治的思想,即遞歸處理小范圍信息,之后將處理的信息合並上傳。一般來說,都是先處理左區間,之后用左區間更新右區間,順便更新答案,然后處理右區間,之后再將兩個區間的信息合並。

通常來說,為了簡化時間復雜度與常數,有幾種方法可以參考

(1)在分治之前先按照某一關鍵字排序,之后在分治過程中,將信息按照時間分成前后兩部分,這樣避免了多次排序。

(2)在分治過程中,利用歸並排序的方式將兩個有序序列合並,將O(nlog)的排序變為O(n)的歸並。

(3)在分治過程中,利用樹狀數組解決問題,除非必須用到別的東西。

(4)在分治過程中,利用有序的性質可以發現,逆序也是有序的,並且滿足一些正好與正序相反,這樣可以避免重復排序。

(5)在分治之前盡可能的簡化不必要的信息,這樣能減少整個代碼的常數。

(6)另外,在更新右區間或者合並的時候,盡量選擇常數與時間復雜度較小的算法,比如說能用單調隊列就不要用斜率優化,能用斜率優化就不要用決策單調性。

其實本身CDQ分治的常數還是蠻小的,並不用太多的東西來優化,卡一卡rank1什么的可以優化一下...

注意事項:

分治結構一般的注意事項:不要用memset之類的東西!包括點分治在內的分治算法,時間復雜度的正確性關鍵在於快速處理出小部分的信息,而這么一個memset真的是自尋死路...(除非你能把握好sizeof里面的東西多大...)

注意出現某一信息相等的時候,記得將再判斷時間的順序,讓時間小的放在前面。(其實沒什么必要,但是能讓你的代碼好調一點)

注意一些比較惡心的邊界條件...

擴充:

CDQ分治也是一種分治結構,可以和點分治結合(比如說:NOI2014購票)

CDQ分治可以套CDQ分治來解決四維偏序問題(當然,萬一出題人毒瘤,出五維偏序怎么辦?目測可以CDQ分治套CDQ分治套樹套樹)

例題時間:

BZOJ3262: 陌上花開

分析:比較裸的三維偏序,一維排序,一維分治,一維樹狀數組

具體實現是將先按x排序,之后按y分治,將z存入樹狀數組中,每次左區間修改,右區間查詢即可。

附上代碼:

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <iostream>
using namespace std;
#define N 100005
struct node
{
	int x,y,z,cnt,num;
	friend bool operator!=(const node &a,const node &b)
	{
		return a.x!=b.x||a.y!=b.y||a.z!=b.z;
	}
	friend bool operator<(const node &a,const node &b)
	{
		return ((a.x==b.x)&(a.y==b.y)&(a.z<b.z))|((a.x==b.x)&(a.y<b.y))|(a.x<b.x);
	}
}a[N],v[N],tmp[N];
int n,sum[N<<1],maxn,ans[N];
void fix(int x,int v){for(;x<=maxn;x+=x&-x)sum[x]+=v;}
int find(int x){int ret=0;for(;x;x-=x&-x)ret+=sum[x];return ret;}
void solve(int l,int r)
{
	if(l==r)return ;
	int m=(l+r)>>1,i=l,j=m+1,p=l;
	solve(l,m);solve(m+1,r);
	while(i<=m&&j<=r)
	{
		if(v[i].y<=v[j].y)fix(v[i].z,v[i].cnt),tmp[p++]=v[i++];
		else tmp[p]=v[j],tmp[p++].num+=find(v[j++].z);
	}
	while(i<=m)fix(v[i].z,v[i].cnt),tmp[p++]=v[i++];
	while(j<=r)tmp[p]=v[j],tmp[p++].num+=find(v[j++].z);
	for(int i=l;i<=m;i++)fix(v[i].z,-v[i].cnt);
	for(int i=l;i<=r;i++)v[i]=tmp[i];
}
int main()
{
	scanf("%d%d",&n,&maxn);
	for(int i=1;i<=n;i++)scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
	sort(a+1,a+n+1);int tot=0;a[0].x=-1;
	for(int i=1;i<=n;i++){if(a[i]!=a[i-1])v[++tot]=a[i];v[tot].cnt++;}
	solve(1,tot);
	for(int i=1;i<=tot;i++)ans[v[i].num+v[i].cnt]+=v[i].cnt;
	for(int i=1;i<=n;i++)printf("%d\n",ans[i]);return 0;
}

BZOJ1492: [NOI2007]貨幣兌換Cash

分析:

暴力的DP方程:f[i]=max(f[j]+calc(i,j));另外,有一點很顯然,要買就全買,要賣就全賣,貪心可證,不去證了...

先化簡一下式子:

f[i]=f[j]/(a[j]*rat[j]+b[j])*(a[i]*rat[j]+b[i])

f[i]=a[i]*f[j]/(a[j]*rat[j]+b[j])*rat[j]+b[i]f[j]/(a[j]*rat[j]+b[j])

設g[j]=f[j]/(a[j]*rat[j]+b[j]);

f[i]=a[i]*rat[j]*g[j]+b[i]*g[j];

(f[i]/a[i])=rat[j]*g[j]+(b[i]/a[i])*g[j];

看起來可以斜率優化,但是完全不可做...因為完全沒有單調性...

既然沒有單調性做什么啊!(啪!不做題想死嗎!)

好吧,既然沒有單調性,那么就強行給他單調性!

所以其實單調性這種事情吧,就是想要有就可以有的,對不對?(對什么對!CDQ分治惡心死你)

那么其實本質上,我們需要將所有點按a[i]/b[i]排序,之后得到的序列滿足a[i]/b[i]單調遞增,每次再盡量不改變順序的前提下,將區間分成兩部分滿足前半部分的時間都小於后半部分的時間,之后先遞歸處理左區間,再用左區間建立一個凸包(滿足每個直線的斜率單調遞增),之后用這個凸包更新右區間,再遞歸處理右區間,最后再按照g[i]的大小歸並。

附上代碼:

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <iostream>
using namespace std;
#define N 100005
#define K(x) (p[x].g)
#define B(x) (p[x].g*p[x].rat)
#define Y(x,y) (K(x)*p[y].b+B(x)*p[y].a)
struct node
{
    double a,b,f,g,k,rat,idx;
    friend bool operator<(const node &a,const node &b)
    {
        return a.k>b.k;
    }
}p[N],pp[N];
int n,q[N],h,t;
bool cmp(int i,int j,int k)
{
    double t1=(B(i)-B(j))*(K(k)-K(i));
    double t2=(B(k)-B(i))*(K(i)-K(j));
    return t1<=t2;
}
void solve(int l,int r)
{
    if(l==r)
    {
        p[l].f=max(p[l].f,p[l-1].f);
        p[l].g=p[l].f/(p[l].a*p[l].rat+p[l].b);
        return ;
    }
    int m=(l+r)>>1,h1=l,h2=m+1;double maxx=0;
    for(int i=l;i<=r;i++)
    {
        if(p[i].idx<=m)pp[h1++]=p[i];
        else pp[h2++]=p[i];
    }
    for(int i=l;i<=r;i++)p[i]=pp[i];solve(l,m);
    int h=1,t=0;
    for(int i=l;i<=m;i++)
    {
        while(h<t&&cmp(q[t],q[t-1],i))t--;
        q[++t]=i;maxx=max(maxx,p[i].f);
    }
    for(int i=m+1;i<=r;i++)
    {
        while(h<t&&Y(q[h],i)<=Y(q[h+1],i))h++;
        p[i].f=max(max(p[i].f,maxx),Y(q[h],i));
        p[i].g=p[i].f/(p[i].a*p[i].rat+p[i].b);
    }
    solve(m+1,r);h1=l,h2=m+1;
    for(int i=l;i<=r;i++)
    {
        if(h1<=m&&(h2>r||K(h1)<K(h2)))pp[i]=p[h1++];
        else pp[i]=p[h2++];
    }
    for(int i=l;i<=r;i++)p[i]=pp[i];
}
int main()
{
    scanf("%d%lf",&n,&p[1].f);
    for(int i=1;i<=n;i++)
    {
        scanf("%lf%lf%lf",&p[i].a,&p[i].b,&p[i].rat);
        p[i].k=-p[i].b/p[i].a;p[i].idx=i;
    }
    sort(p+1,p+n+1);
    solve(1,n);double ans=0;
    for(int i=1;i<=n;i++)ans=max(ans,p[i].f);
    printf("%.3lf\n",ans);
}

BZOJ3295: [Cqoi2011]動態逆序對

當我第一次看到這道題的時候,“這不是主席樹+樹狀數組裸題嘛!”之后樹套樹水過。

當我再次看到這道題的時候...“卧槽,這題還能用CDQ分治?!”之后CDQ分治水過。

類似陌上花開,刪除就相當於加入,之后加入是逆序的

附上代碼:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstdlib>
#include <cstring>
using namespace std;
#define N 100005
struct node
{
	int x,p,t,num;
	friend bool operator<(const node &a,const node &b)
	{
		if(a.t==b.t)return a.p<b.p;
		return a.t<b.t;
	}
}p[N],pp[N];
int sum[N],n,Q,b[N];long long ans[N],tot;
void fix(int x,int c){for(;x<=n;x+=x&-x)sum[x]+=c;}
int find(int x){int ret=0;for(;x;x-=x&-x)ret+=sum[x];return ret;}
void solve(int l,int r)
{
	if(l==r)return ;int m=(l+r)>>1,h1=l,h2=m+1;
	/*for(int i=l;i<=r;i++){if(p[i].p<=m)pp[h1++]=p[i];else pp[h2++]=p[i];}
	for(int i=l;i<=r;i++)p[i]=pp[i];*/solve(l,m);solve(m+1,r);h1=l,h2=m+1;
	for(int i=l;i<=r;i++)
	{
		if(h1<=m&&(h2>r||p[h1].p<p[h2].p))pp[i]=p[h1],fix(p[h1++].x,1);
		else pp[i]=p[h2],ans[pp[i].t]+=find(n)-find(p[h2++].x);
	}for(int i=l;i<=m;i++)fix(p[i].x,-1);h1=m,h2=r;
	for(int i=l;i<=r;i++)
	{
		if(h1>=l&&(h2<m+1||p[h1].p>p[h2].p))fix(p[h1--].x,1);
		else ans[p[h2].t]+=find(p[h2].x-1),h2--;
	}for(int i=l;i<=m;i++)fix(p[i].x,-1);for(int i=l;i<=r;i++)p[i]=pp[i];
}
int main()
{
	scanf("%d%d",&n,&Q);
	for(int i=1;i<=n;i++)scanf("%d",&p[i].x),p[i].p=i,b[p[i].x]=i,p[i].t=1;
	for(int i=1,x;i<=Q;i++)scanf("%d",&x),p[b[x]].t=n-i+1;sort(p+1,p+n+1);
	solve(1,n);for(int i=1;i<=n;i++)tot+=ans[i];
	for(int i=n;i>n-Q;i--)printf("%lld\n",tot),tot-=ans[i];
	//for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);
}

樹套樹:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstdlib>
#include <cstring>
using namespace std;
#define N 100005
struct node{int ls,rs,siz;}tr[N*100];
int nx,ny,rx[N],ry[N],cnt,Q,n,rot[N],a[N],p[N],q[N],vis[N];long long ans[N];
void insert(int v,int c,int l,int r,int &rt)
{
	if(!rt)rt=++cnt;tr[rt].siz+=c;if(l==r)return ;int m=(l+r)>>1;
	if(m>=v)insert(v,c,l,m,tr[rt].ls);else insert(v,c,m+1,r,tr[rt].rs);
}
int query_back(int l,int r,int x)
{
	if(l==r)return 0;int m=(l+r)>>1,sizls=0;
	for(int i=1;i<=nx;i++)sizls-=tr[tr[rx[i]].ls].siz;
	for(int i=1;i<=ny;i++)sizls+=tr[tr[ry[i]].ls].siz;
	if(m>=x)
	{
		for(int i=1;i<=nx;i++)rx[i]=tr[rx[i]].ls;
		for(int i=1;i<=ny;i++)ry[i]=tr[ry[i]].ls;
		return query_back(l,m,x);
	}
	for(int i=1;i<=nx;i++)rx[i]=tr[rx[i]].rs;
	for(int i=1;i<=ny;i++)ry[i]=tr[ry[i]].rs;
	return sizls+query_back(m+1,r,x);
}
int query_front(int l,int r,int x)
{
	if(l==r)return 0;int m=(l+r)>>1,sizrs=0;
	for(int i=1;i<=nx;i++)sizrs-=tr[tr[rx[i]].rs].siz;
	for(int i=1;i<=ny;i++)sizrs+=tr[tr[ry[i]].rs].siz;
	if(m>=x)
	{
		for(int i=1;i<=nx;i++)rx[i]=tr[rx[i]].ls;
		for(int i=1;i<=ny;i++)ry[i]=tr[ry[i]].ls;
		return query_front(l,m,x)+sizrs;
	}
	for(int i=1;i<=nx;i++)rx[i]=tr[rx[i]].rs;
	for(int i=1;i<=ny;i++)ry[i]=tr[ry[i]].rs;
	return query_front(m+1,r,x);
}
void pre(int l,int r)
{
	nx=ny=0;
	for(int i=l;i;i-=i&-i)rx[++nx]=rot[i];
	for(int i=r;i;i-=i&-i)ry[++ny]=rot[i];
}
int main()
{
	scanf("%d%d",&n,&Q);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		p[a[i]]=i;
	}
	for(int i=1;i<=Q;i++)
	{
		scanf("%d",&q[i]);
		vis[q[i]]=1;
	}
	for(int i=n;i>=1;i--)
	{
		if(!vis[a[i]])
		{
			pre(0,n);ans[Q+1]+=query_back(1,n,a[i]);
			for(int j=i;j<=n;j+=j&-j)insert(a[i],1,1,n,rot[j]);
		}
	}
	for(int i=Q;i;i--)
	{
		ans[i]=ans[i+1];
		for(int j=p[q[i]];j<=n;j+=j&-j)insert(q[i],1,1,n,rot[j]);
		pre(p[q[i]],n);ans[i]+=query_back(1,n,q[i]);
		pre(0,p[q[i]]);ans[i]+=query_front(1,n,q[i]);
	}
	for(int i=1;i<=Q;i++)
	{
		printf("%lld\n",ans[i]);
	}
}

就是慢了點,沒關系的...

BZOJ1176: [Balkan2007]Mokia&BZOJ2683: 簡單題

說實話,這題寫KD-Tree真的是好寫...但是nsqrt(n)跑不過nloglog也是正常的對不對?

單點修改就直接相當於添加一個點,查詢就相當於拆成四個。按照時間戳排序,按照x大小歸並

附上代碼:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstdlib>
#include <cstring>
using namespace std;
#define N 200005
struct node
{
    int x,y,op,c,idx;
}p[N],pp[N];
int n,Q,maxn,num;long long sum[N*10],ans[N];
void fix(int x,int c){for(;x<=maxn;x+=x&-x)sum[x]+=c;}
int find(int x){int ret=0;for(;x;x-=x&-x)ret+=sum[x];return ret;}
void solve(int l,int r)
{
    if(l==r)return ;int m=(l+r)>>1,h1=l,h2=m+1;
    solve(l,m);solve(m+1,r);h1=l,h2=m+1;
    for(int i=l;i<=r;i++)
    {
        if(h1<=m&&(h2>r||p[h1].x<=p[h2].x)){if(!p[h1].op)fix(p[h1].y,p[h1].c);pp[i]=p[h1++];}
        else{if(p[h2].op)ans[p[h2].idx]+=p[h2].c*find(p[h2].y);pp[i]=p[h2++];}
    }
    for(int i=l;i<=m;i++)if(!p[i].op)fix(p[i].y,-p[i].c);for(int i=l;i<=r;i++)p[i]=pp[i];
}
int main()
{
    scanf("%*d%d",&maxn);
    while(1)
    {
        int op,x,y,a,b;
        scanf("%d",&op);//%d%d%d",&op,&x,&y,&a);
        if(op==3)break;
        if(op==1)
        {
            scanf("%d%d%d",&x,&y,&a);
            p[++n]=(node){x,y,0,a,0};
        }else
        {
            num++;
            scanf("%d%d%d%d",&x,&y,&a,&b);x--,y--;
            p[++n]=(node){a,b,1,1,num};
            p[++n]=(node){a,y,1,-1,num};
            p[++n]=(node){x,b,1,-1,num};
            p[++n]=(node){x,y,1,1,num};
        }
    }
    solve(1,n);
    for(int i=1;i<=num;i++)printf("%lld\n",ans[i]);
}

BZOJ2225: [Spoj 2371]Another Longest Increasing

分析:二維LIS,CDQ分治正常操作,按照第一維排序,每次取出左右區間,之后按照第一維歸並。第二維樹狀數組即可。

附上代碼:

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <iostream>
using namespace std;
#define N 200005
struct node
{
    int a,b,idx;
}p[N],pp[N];
int n,maxx[N],f[N],t[N];
void fix(int x,int c){for(;x<N;x+=x&-x)maxx[x]=max(maxx[x],c);}
void clear(int x){for(;x<N;x+=x&-x)maxx[x]=0;}
int find(int x){int ret=0;for(;x;x-=x&-x)ret=max(maxx[x],ret);return ret;}
void solve(int l,int r)
{
    if(l==r)return;
    int m=(l+r)>>1,h1=l,h2=m+1;
    for(int i=l;i<=r;i++)
    {
        if(p[i].idx<=m)pp[h1++]=p[i];
        else pp[h2++]=p[i];
    }for(int i=l;i<=r;i++)p[i]=pp[i];
    solve(l,m);h1=l,h2=m+1;
    for(int i=m+1,j=l;i<=r;i++)
    {
        while(p[j].a<p[i].a&&j<=m)fix(p[j].b,f[p[j].idx]),j++;
        f[p[i].idx]=max(f[p[i].idx],find(p[i].b-1)+1);
    }for(int i=l;i<=m;i++)clear(p[i].b);
    solve(m+1,r);h1=l,h2=m+1;
    for(int i=l;i<=r;i++)
    {
        if(h1<=m&&(h2>r||p[h1].a<p[h2].a))pp[i]=p[h1++];
        else pp[i]=p[h2++];
    }for(int i=l;i<=r;i++)p[i]=pp[i];
}
bool cmp(const node &a,const node &b){return a.a<b.a;}
int main()
{
    scanf("%d",&n);f[1]=1;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&p[i].a,&p[i].b);p[i].idx=i;t[i]=p[i].b;
    }sort(t+1,t+n+1);
    for(int i=1;i<=n;i++)p[i].b=lower_bound(t+1,t+n+1,p[i].b)-t;
    sort(p+1,p+n+1,cmp);solve(1,n);int ans=0;
    for(int i=1;i<=n;i++)ans=max(ans,f[i]);//printf("%d\n",f[i]);
    printf("%d\n",ans);
}

BZOJ2716: [Violet 3]天使玩偶

CDQ分治可以做,別問我,反正CDQ分治還跑不過KD-Tree呢!

BZOJ3672: [Noi2014]購票

直接上鏈接就可以了...我寫過這篇題解!https://www.cnblogs.com/Winniechen/p/9247939.html

那么CDQ分治就到這里了...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM