CDQ 专题

前言中的前言

  1. 由于本人过菜,有些题解会咕掉,请原谅这个蒟蒻

  2. 由于本人过菜,不知道什么时候就 AFO 了,想给这个机房留下点什么……

  3. 如果想看高效进阶的题解,建议出门左拐,去云落那里看看,保证是全网最全最好的,但不要对云落的博客好奇,更不要看云落的一言 和 云落的合集:黑夜刀己,白日爱人

正片开始!

好耶!!!(泪目)

题面

三维偏序(陌上花开)

前言

首先登场的是板子题,借这个题讲一下cdq

正文

准确来说,cdq 是一种思想,本蒟蒻的理解是把区间分成两份,左面的一定会给右面的作出贡献

好的就这道题而言,有三个属性(这里记为 t,x,y

我们先按 t 排序

那么,一个数的左边的数第一维一定符合条件

好的,上面说了 cdq 的思想,现在套上

把这个序列从中间分成两半,此时不管两个序列内部顺序怎么变,对右面的来说,左面的在 t 上一定符合要求

对每一半都按 x 排序

维护两个指针:

维护后一半的指针 i ,前一半的指针 j ,每次将 i 后移一位时,若 xj<=xi 则不断后移j,并不断将 yj 加入树状数组。然后再查询树状数组中有多少数小于等于 yi 。 最后要清空树状数组。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,nn;
struct node{int x,y,z,ans,w;}a[N],b[N];
bool cmp(node a,node b){
	if(a.x==b.x){
		if(a.y==b.y)return a.z<b.z;
		return a.y<b.y;
	}
	return a.x<b.x;
}
bool cmp2(node a,node b){
	if(a.y==b.y)return a.z<b.z;
	return a.y<b.y;
}
int c[N];
int lowbit(int x){return x&-x;}
void add(int x,int d){
	for(;x<=m;x+=lowbit(x))c[x]+=d;
}
int query(int x){
	int res=0;
	for(;x;x-=lowbit(x))res+=c[x];
	return res;
}
void cdq(int l,int r){
	if(l==r)return;
	int mid=(l+r)>>1;
	cdq(l,mid),cdq(mid+1,r);
	sort(a+l,a+mid+1,cmp2);
	sort(a+mid+1,a+1+r,cmp2);
	int i=mid+1,j=l;
	while(i<=r){
		while(a[j].y<=a[i].y&&j<=mid){
			add(a[j].z,a[j].w);
			j++;
		}
		a[i].ans+=query(a[i].z);
		i++;
	}
	for(int i=l;i<j;i++){
		add(a[i].z,-a[i].w);
	}
}
int cnt[N];
int main(){
	cin>>nn>>m;
	for(int i=1;i<=nn;i++)cin>>b[i].x>>b[i].y>>b[i].z;
	sort(b+1,b+1+nn,cmp);
	int c=0;
	for(int i=1;i<=nn;i++){
		c++;
		if(b[i].x!=b[i+1].x||b[i].y!=b[i+1].y||b[i].z!=b[i+1].z){
			a[++n]=b[i],a[n].w=c,c=0;
		}
	}
	cdq(1,n);
	for(int i=1;i<=n;i++){
		cnt[a[i].ans+a[i].w-1]+=a[i].w;
	}
	for(int i=0;i<nn;i++)cout<<cnt[i]<<endl;
	return 0;
}

后记

四维偏序在最后面!

题面

题目传送门

前言

再来道没有那么应用的题

正文

正难则反?

你会发现正并不难

只要记录每次删除对逆序对产生的贡献即可

还是记 t,x,y 三维

发现对答案产生贡献的点对满足 ti<tj ( j 要比 i 后删除)xj<xi yi>yjti<tj xj>xi yi<yj

好的

三维数点

在 cdq 里面正着来一遍,反着来一遍即可

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+6;
#define int long long
int n,m,po[N],tot,ans[N],c[N];
struct node{int o,pos,val,id,t;}p[N];
bool cmp(node a,node b){return a.pos<b.pos;}
int lowbit(int x){return x&-x;}
void add(int x,int d){
    for(;x<=n;x+=lowbit(x))c[x]+=d;
}
int query(int x){
    int res=0;
    for(;x;x-=lowbit(x))res+=c[x];
    return res;
}
void cdq(int l,int r){
    if(l==r)return;
    int mid=(l+r)>>1;
    cdq(l,mid),cdq(mid+1,r);
    sort(p+l,p+mid+1,cmp),sort(p+mid+1,p+r+1,cmp);
    int j=l,i=mid+1;
    while(i<=r){
        while(j<=mid&&p[j].pos<=p[i].pos){
            add(p[j].val,p[j].o);
            j++;
        }
        ans[p[i].id]+=p[i].o*(query(n)-query(p[i].val));
        i++;
    }
    for(int i=l;i<j;i++)add(p[i].val,-p[i].o);
    j=mid,i=r;
    while(i>mid){
        while(j>=l&&p[j].pos>=p[i].pos){
            add(p[j].val,p[j].o);
            j--;
        }
        ans[p[i].id]+=p[i].o*query(p[i].val-1);
        i--;
    }
    for(int i=mid;i>j;i--)add(p[i].val,-p[i].o);
}
signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int x;cin>>x;
        po[x]=i;
        p[++tot]=node{1,i,x,0,tot};
    }
    for(int i=1;i<=m;i++){
        int x;cin>>x;
        p[++tot]=node{-1,po[x],x,i,tot};
    }
    cdq(1,tot);
    for(int i=1;i<=m;i++)ans[i]+=ans[i-1];
    for(int i=0;i<m;i++)cout<<ans[i]<<endl;
    return 0;
}

后记

也很板叭(博客没保存!!!)

题面

题目传送门

前言

小容斥

正文

只算每个点左下角就是三维数点

加上小容斥

ans=(x1,y1)+(x,y)(x1,y)(x,y1)

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2e6+5;
struct node{int t,x,y,id,val;}p[N];
int w,cnt;
bool cmp(node x,node y){return x.x<y.x;}
int c[N];
int lowbit(int x){return x&-x;}
void add(int x,int y){
    for(;x<=w;x+=lowbit(x))c[x]+=y;
}
int query(int x){
    int res=0;
    for(;x;x-=lowbit(x))res+=c[x];
    return res;
}
void cdq(int l,int r){
    if(l==r)return;
    int mid=(l+r)>>1;
    cdq(l,mid),cdq(mid+1,r);
    sort(p+l,p+mid+1,cmp),sort(p+mid+1,p+r+1,cmp);
    int i=mid+1,j=l;
    while(i<=r){
        while(j<=mid&&p[j].x<=p[i].x){
            if(!p[j].id)add(p[j].y,p[j].val);
                j++;
        }
        if(p[i].id)p[i].val+=query(p[i].y);
        i++;
    }
    for(int i=l;i<j;i++)if(!p[i].id)add(p[i].y,-p[i].val);
}
bool cmp1(node a,node b){return a.t<b.t;}
int main(){
    int o;
    cin>>o>>w;
    w++;
    while(o!=3){
        cin>>o;
        if(o==1){
            int x,y,a;
            cin>>x>>y>>a;
            p[++cnt]=node{cnt,x+1,y+1,0,a};
        }else if(o==2){
            int x,y,x2,y2;
            cin>>x>>y>>x2>>y2;
            x2++,y2++;
            p[++cnt]=node{cnt,x,y,1,0};
            p[++cnt]=node{cnt,x2,y2,1,0};
            p[++cnt]=node{cnt,x2,y,1,0};
            p[++cnt]=node{cnt,x,y2,1,0};
        }
    }
    cdq(1,cnt);
    sort(p+1,p+1+cnt,cmp1);
    for(int i=1;i<=cnt;i++){
        if(p[i].id){
            cout<<p[i].val+p[i+1].val-p[i+2].val-p[i+3].val<<endl;
            i+=3;
        }
    }
    return 0;
}

后记

何去何往

题面

题目传送门

前言

cdq 优化 dp 捏

正文

这题是在导弹拦截上又加上了一维限制

定义:

dp1[i]表示以第i枚导弹为结尾的方案中的最长不上升子序列长度

dp2[i]表示以第i枚导弹为开头的方案中的最长不上升子序列长度

f1[i]表示以第i枚导弹为结尾的最长不上升子序列方案总数

f2[i]表示以第i枚导弹为开头的最长不上升子序列方案总数

那么第一问的答案即max{dp1[i]}

第二问第i枚导弹的答案即f1[i]*f2[i]/k 且dp1[i]+dp2[i]=ans-1,ans为第一问答案,k为总方案数

还是三维 t,x,y

不过这次要把 t 扔进树状数组里

为什么捏

因为

懒!

我总不能对一个排列离散化叭

cdq 时,你会发现,是左边对右边产生贡献

所以要先算完左边在进行合并

void cdq(int l,int r,int o){
    if(l==r)return;
    int mid=(l+r)>>1;
    cdq(l,mid,o);
    if(o==1){
        sort(a+l,a+mid+1,cmp3);
        sort(a+mid+1,a+r+1,cmp3);
    }
    else{
        sort(a+l,a+mid+1,cmp4);
        sort(a+mid+1,a+r+1,cmp4);
    }
    merge(l,r,o);
    if(o==1)sort(a+mid+1,a+r+1,cmp1);
    else sort(a+mid+1,a+r+1,cmp2);
    cdq(mid+1,r,o);
}

merge 就是合并,步骤就是 dp

void merge(int l,int r,int o){
    int mid=(l+r)>>1;
    int j=l,i=mid+1;
    while(j<=mid&&i<=r){
        if((o==1&&a[j].y>=a[i].y)||(o==2&&a[j].y<=a[i].y)){
            add(a[j].z,a[j].dp,a[j].f);
            j++;
        }else{
            int val=query1(a[i].z)+1;
            if(a[i].dp<val){
                a[i].dp=val;
                a[i].f=query2(a[i].z,val-1);
            }else if(a[i].dp==val){
                a[i].f+=query2(a[i].z,val-1);
            }
            i++;
        }
    }
    while(i<=r){
        int val=query1(a[i].z)+1;
        if(a[i].dp<val){
            a[i].dp=val;
            a[i].f=query2(a[i].z,val-1);
        }else if(a[i].dp==val){
            a[i].f+=query2(a[i].z,val-1);
        }
        i++;       
    }
    for(int i=l;i<j;i++)del(a[i].z);
}

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+6;
int n;
struct node{int x,y,z,dp;double f;}a[N];
bool cmp1(node a,node b){
    if(a.x==b.x){
        if(a.y==b.y)return a.z<b.z;
        return a.y>b.y;
    }   
    return a.x>b.x;
}
bool cmp2(node a,node b){
    if(a.x==b.x){
        if(a.y==b.y)return a.z<b.z;
        return a.y<b.y;
    }   
    return a.x<b.x;
}
bool cmp3(node a,node b){
    return a.y>b.y;
}
bool cmp4(node a,node b){
    return a.y<b.y;
}
int lowbit(int x){return x&-x;}
int c1[N];
double c2[N];
void add(int x,int dp,double f){
    for(;x<=n;x+=lowbit(x)){
        if(c1[x]<dp){
            c1[x]=dp;
            c2[x]=f;
        }else if(c1[x]==dp)c2[x]+=f;
    }
}
int query1(int x){
    int res=0;
    for(;x;x-=lowbit(x))res=max(res,c1[x]);
    return res;
}
double query2(int x,int dp){
    double res=0;
    for(;x;x-=lowbit(x))if(c1[x]==dp)res+=c2[x];
    return res;
}
void del(int x){
    for(;x<=n;x+=lowbit(x))c1[x]=c2[x]=0;
}
void merge(int l,int r,int o){
    int mid=(l+r)>>1;
    int j=l,i=mid+1;
    while(j<=mid&&i<=r){
        if((o==1&&a[j].y>=a[i].y)||(o==2&&a[j].y<=a[i].y)){
            add(a[j].z,a[j].dp,a[j].f);
            j++;
        }else{
            int val=query1(a[i].z)+1;
            if(a[i].dp<val){
                a[i].dp=val;
                a[i].f=query2(a[i].z,val-1);
            }else if(a[i].dp==val){
                a[i].f+=query2(a[i].z,val-1);
            }
            i++;
        }
    }
    while(i<=r){
        int val=query1(a[i].z)+1;
        if(a[i].dp<val){
            a[i].dp=val;
            a[i].f=query2(a[i].z,val-1);
        }else if(a[i].dp==val){
            a[i].f+=query2(a[i].z,val-1);
        }
        i++;       
    }
    for(int i=l;i<j;i++)del(a[i].z);
}
void cdq(int l,int r,int o){
    if(l==r)return;
    int mid=(l+r)>>1;
    cdq(l,mid,o);
    if(o==1){
        sort(a+l,a+mid+1,cmp3);
        sort(a+mid+1,a+r+1,cmp3);
    }
    else{
        sort(a+l,a+mid+1,cmp4);
        sort(a+mid+1,a+r+1,cmp4);
    }
    merge(l,r,o);
    if(o==1)sort(a+mid+1,a+r+1,cmp1);
    else sort(a+mid+1,a+r+1,cmp2);
    cdq(mid+1,r,o);
}
int dp1[N],dp2[N];
double f1[N],f2[N];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i].x>>a[i].y;
        a[i].z=i,a[i].dp=1,a[i].f=1;
    }
    sort(a+1,a+1+n,cmp1);
    cdq(1,n,1);
    int ans=0;
    for(int i=1;i<=n;i++){
        dp1[a[i].z]=a[i].dp;
        f1[a[i].z]=a[i].f;
        ans=max(ans,dp1[a[i].z]);
    }
    cout<<ans<<endl;
    for(int i=1;i<=n;i++){
        a[i].z=n-a[i].z+1;
        a[i].dp=a[i].f=1;
    }
    sort(a+1,a+n+1,cmp2);
    cdq(1,n,2);
    double k=0;
    for(int i=1;i<=n;i++){
        dp2[n-a[i].z+1]=a[i].dp;
        f2[n-a[i].z+1]=a[i].f;
        if(dp2[n-a[i].z+1]==ans)k+=f2[n-a[i].z+1];
    }
    for(int i=1;i<=n;i++){
        if(dp1[i]+dp2[i]==ans+1)printf("%.5lf ",f1[i]*f2[i]/k);
        else cout<<"0.00000 ";
    }
    return 0;
} 

后记

明明心里很清楚,但还是要问……

题面

题目传送门

前言

四维数点来喽

不过芙宁娜世界第一可爱(万叶叶用可爱形容不太好叭)

正文

思考一下三维偏序中的 CDQ 分治做法:

本质就是把每个 (x,y,z) 转化若干为 (0,y,z)(1,y,z) 的贡献。

四维偏序中珂以沿用三维偏序中的思路。

把若干 (x,y,z,w) 分成 (0/1,0/1,z,w),然后计算 (0,0,z,w)(1,1,z,w) 的贡献。

第二层 CDQ 和三维偏序中的 CDQ 差不多。

每个元素需要再加个变量 o 。表示的意思就是第一维的 0/1 情况。

第一层 CDQ 的时候把 lmid 这一段的 o 设成 0 (即第一维为 0 ),mid+1r 这一段的 ok 设成 1。

第二层的改变是:插入的时候只用当 o=0 的时候才能插入,询问的时候只有当 o=1 的时候才能询问。

同样这个做法可以扩展到 k 维:用若干个变量记录前面几维的 0/1 状态。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+6;
const int inf=0x3f3f3f3f3f3f3f3fll;
int n,b[N],tot;
struct node{int x,y,z,w,val,o,mx,id;}a[N],tmp[N];
bool cmp1(node a,node b){
    if(a.x==b.x){
        if(a.y==b.y){
            if(a.z==b.z){
                if(a.w==b.w)return a.val<b.val;
                return a.w<b.w;
            }
            return a.z<b.z;
        }
        return a.y<b.y;
    }
    return a.x<b.x;
}
bool cmp2(node a,node b){
    if(a.y==b.y){
        if(a.z==b.z){
            if(a.w==b.w)return a.val<b.val;
            return a.w<b.w;
        }
        return a.z<b.z;
    }
    return a.y<b.y;   
}
bool cmp3(node a,node b){
    if(a.z==b.z){
        if(a.w==b.w)return a.val<b.val;
        return a.w<b.w;
    }
    return a.z<b.z;
}
int y[N],y2[N],c[N];
int lowbit(int x){return x&-x;}
void add(int x,int d){
    for(;x<=n;x+=lowbit(x))c[x]=max(c[x],d);
}
int query(int x){
    int res=-inf;
    for(;x;x-=lowbit(x))res=max(res,c[x]);
    return res;
}
void clear(int x){
    for(;x<=n;x+=lowbit(x))c[x]=-inf;
}
void cdq2(int l,int r){
    if(l==r)return;
    int mid=(l+r)>>1;
    cdq2(l,mid);
    sort(a+l,a+mid+1,cmp3);
    sort(a+mid+1,a+r+1,cmp3);
    int j=l,i=mid+1;
    while(i<=r){
        while(j<=mid&&a[j].z<=a[i].z){
            if(a[j].o)add(a[j].w,a[j].mx);
            j++;
        }
        if(!a[i].o)a[i].mx=max(a[i].mx,query(a[i].w)+a[i].val);
        i++;
    }
    for(int i=l;i<j;i++)if(a[i].o)clear(a[i].w);
    for(int i=l;i<=r;i++)tmp[y2[a[i].id]]=a[i];
    for(int i=l;i<=r;i++)a[i]=tmp[i];
    cdq2(mid+1,r);
}
void cdq(int l,int r){
    if(l==r)return;
    int mid=(l+r)>>1;
    cdq(l,mid);
    for(int i=l;i<=mid;i++)a[i].o=1;
    for(int i=mid+1;i<=r;i++)a[i].o=0;
    sort(a+l,a+r+1,cmp2);
    for(int i=l;i<=r;i++)y2[a[i].id]=i;
    cdq2(l,r);
    for(int i=l;i<=r;i++)tmp[y[a[i].id]]=a[i];
    for(int i=l;i<=r;i++)a[i]=tmp[i];
    cdq(mid+1,r);
}
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i].x>>a[i].y>>a[i].z>>a[i].w>>a[i].val;
        b[i]=a[i].w;
    }
    // sort(b+1,b+1+n);
    // tot=unique(b+1,b+1+n)-b-1;
    // for(int i=1;i<=n;i++)a[i].w=lower_bound(b+1,b+1+tot,a[i].w)-b;
    sort(a+1,a+n+1,cmp1);
    int cnt=0;
    for(int i=1;i<=n;i++){
        if((a[i].x==a[i-1].x&&a[i].y==a[i-1].y)&&(a[i].z==a[i-1].z&&a[i].w==a[i-1].w)){
            a[cnt].val+=max(0ll,a[i].val);
        }else{
            a[++cnt]=a[i];
        }
    }
    for(int i=1;i<=n;i++)a[i].mx=a[i].val,a[i].id=i,y[a[i].id]=i;
    //memset(c,-inf,sizeof(c));
    cdq(1,n);
    int ans=-inf;
    for(int i=1;i<=n;i++)ans=max(ans,a[i].mx);
    cout<<ans;
    return 0;
}

后记

真希望从没遇见过你们

过去的我

很享受独处呢

posted @   小惰惰  阅读(9)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
/* 鼠标点击求赞文字特效 */
点击右上角即可分享
微信分享提示

目录导航