CDQ 分治 学习笔记

CDQ 分治。

问题引入:P3810 【模板】三维偏序(陌上花开)

题目要我们求 $ a_j \leq a_i $ 且 $ b_j \leq b_i $ 且 $ c_j \leq c_i $ 且 $ j \ne i $ 的 \(j\) 的数量。

我们可以 sort 第一维,归并排序排第二维,树状数组求第三维。

由于题目可以取等,并且是 ≤ 偏序,所以我们要魔改一下 merge 函数。

指针跳的顺序如下:

令左指针为 \(i\),右指针为 \(j\)。如果跳的是左指针,就在树状数组中查 \(\le c_i\) 的元素个数,统计答案;如果跳的是右指针,就在树状数组中加入 \(cnt[j]\)\(c_j\)

注意:在最后需要清空树状数组。注意别出锅。

代码中的 Hash 函数是判断两组元素是否相等。

输出时输出 \(n\) 个数,而不是去重后的数的个数。

Code:


#include<bits/stdc++.h>
// #define int long long

using namespace std;

const int Size=(1<<20)+1;
char buf[Size],*p1=buf,*p2=buf;
char buffer[Size];
int op1=-1;
const int op2=Size-1;
#define getchar()                                                              \
(tt == ss && (tt=(ss=In)+fread(In, 1, 1 << 20, stdin), ss == tt)     \
	? EOF                                                                 \
	: *ss++)
char In[1<<20],*ss=In,*tt=In;
inline int read()
{
	int x=0,c=getchar(),f=0;
	for(;c>'9'||c<'0';f=c=='-',c=getchar());
	for(;c>='0'&&c<='9';c=getchar())
		x=(x<<1)+(x<<3)+(c^48);
	return f?-x:x;
}
inline void write(int x)
{
	if(x<0) x=-x,putchar('-');
	if(x>9)  write(x/10);
	putchar(x%10+'0');
}

struct Node{
    int x,y,z;
    int ans,cnt;
}a[100005],b[100005];
bool vis[100005];
int ans[100005];
bool cmp(Node x,Node y) { return (x.x>y.x||(x.x==y.x&&x.y>y.y)||(x.x==y.x&&x.y==y.y&&x.z>y.z)); }
long long Hash(Node x) { return (x.x+1)*1000000ll*1000000+(x.y+1)*1000000ll+(x.z+1); }

int n,k;
// unordered_map<int,int> cnt;
int t[200005];
const int MAXN=200001;
inline int lowbit(int x) { return x&(-x); }
void add(int p,int k)
{
    for(int i=p;i<=MAXN;i+=lowbit(i)) t[i]+=k;
}

int query(int l,int r)
{
    int ans=0;
    for(int i=l-1;i>0;i-=lowbit(i)) ans-=t[i];
    for(int i=r;i>0;i-=lowbit(i)) ans+=t[i];
    return ans;
}

void merge(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    merge(l,mid);
    merge(mid+1,r);

    int i=mid,j=r,k=r;
    while(i>=l&&j>mid)
    {
        if(a[j].y<=a[i].y)
        {
            vis[k]=1;
            add(a[j].z,a[j].cnt);
            b[k--]=a[j--];
        }
        else
        {
            a[i].ans+=query(1,a[i].z);
            b[k--]=a[i--];
        }
    }
    while(i>=l)
    {
        a[i].ans+=query(1,a[i].z);
        b[k--]=a[i--];
    }
    while(j>mid) b[k--]=a[j--];
    for(int i=l;i<=r;i++)
    {
        a[i]=b[i];
        if(vis[i]) add(a[i].z,-a[i].cnt),vis[i]=0;
    }
}

signed main()
{
    n=read();
    k=read();
    for(int i=1;i<=n;i++)
    {
        a[i].x=read();
        a[i].y=read();
        a[i].z=read();
    }
    sort(a+1,a+1+n,cmp);
    a[0]={-1,-1,-1};
    int tot=0;
    for(int i=1;i<=n;i++)
    {
        if(Hash(a[i-1])!=Hash(a[i])) b[++tot]=a[i];
        b[tot].cnt++;
    }
    swap(a,b);
    swap(n,tot);

    merge(1,n);
    for(int i=1;i<=n;i++) ans[a[i].ans+a[i].cnt-1]+=a[i].cnt;

    for(int i=0;i<tot;i++)
    {
        write(ans[i]);
        putchar('\n');
    }

	return 0;
}



例题:P4390 [BalkanOI 2007] Mokia 摩基亚

将操作时间 \(i\) 看作第一维,横坐标 \(x\) 看作第二维,纵坐标 \(y\) 看作第三维,题目要我们求三维偏序。

注意归并排序中 \(i\)\(j\) 别写反了。

还是 ≼ 偏序而不是 < 偏序。

拆操作 2 的时候不要脑残把坐标写错了。

#include<bits/stdc++.h>
#define int long long

using namespace std;

inline int read()
{
	int x=0,c=getchar(),f=0;
	for(;c>'9'||c<'0';f=c=='-',c=getchar());
	for(;c>='0'&&c<='9';c=getchar())
		x=(x<<1)+(x<<3)+(c^48);
	return f?-x:x;
}
inline void write(int x)
{
	if(x<0) x=-x,putchar('-');
	if(x>9)  write(x/10);
	putchar(x%10+'0');
}

int MAXN;
int n;
struct Node{
    signed y,z;
    long long ans,cnt,op,id;
}a[1<<18],b[1<<18];
// bool cmp(Node x,Node y) { return x.x<y.x||x.x= }
int tot;
int ans[10005];
bool vis[1<<18];

// const int MA=5e5;
int t[2<<20];
int lowbit(int x) { return x&(-x); }
void add(int p,int k)
{
    for(int i=p;i<=MAXN;i+=lowbit(i)) t[i]+=k;
}
int query(int l,int r)
{
    int ans=0;
    for(int i=l-1;i>0;i-=lowbit(i)) ans-=t[i];
    for(int i=r;i>0;i-=lowbit(i)) ans+=t[i];
    return ans;
}

void merge(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    merge(l,mid);
    merge(mid+1,r);

    int i=l,j=mid+1,k=l;
    // cout<<"l="<<l<<" r="<<r<<"\n";
    while(i<=mid&&j<=r)
    {
        if(a[i].y<=a[j].y)
        {
            if(!a[i].id)
            {
                vis[k]=1;
                add(a[i].z,a[i].cnt);
                // cout<<" a[i].z="<<a[i].z<<" a[i].cnt="<<a[i].cnt<<"\n";
            }
            b[k++]=a[i++];
        }
        else
        {
            if(a[j].id) a[j].ans+=query(1,a[j].z);
            b[k++]=a[j++];
        }
    }
    while(i<=mid) b[k++]=a[i++];
    while(j<=r)
    {
        if(a[j].id) a[j].ans+=query(1,a[j].z);
        b[k++]=a[j++];
    }
    for(int i=l;i<=r;i++)
    {
        a[i]=b[i];
        if(vis[i])
        {
            vis[i]=0;
            add(a[i].z,-a[i].cnt);
        }
    }
    // memset(t,0,sizeof(t));
    // memset(vis,0,sizeof(vis));
    // cout<<"\n";
}

signed main()
{
    cin>>MAXN>>MAXN;
    int op;
    while(cin>>op)
    {
        if(op==3) break;
        if(op==2)
        {
            int x1=read(),y1=read(),x2=read(),y2=read();
            ++tot;
            a[++n]={x1-1,y1-1,0,0,1,tot};
            a[++n]={x1-1,y2,0,0,-1,tot};
            a[++n]={x2,y1-1,0,0,-1,tot};
            a[++n]={x2,y2,0,0,1,tot};
        }
        if(op==1)
        {
            int x=read(),y=read(),z=read();
        	// cout<<"("<<x<<","<<y<<") cnt="<<z<<"\n";
            a[++n]={x,y,0,z,0,0};
        }
    }

    merge(1,n);

    for(int i=1;i<=n;i++)
	{
		// if(a[i].id) cout<<"id="<<a[i].id<<" x="<<a[i].y<<" y="<<a[i].z<<" dt="<<a[i].ans*a[i].op<<"\n";
		ans[a[i].id]+=a[i].ans*a[i].op;
	}

    for(int i=1;i<=tot;i++)
    write(ans[i]),putchar('\n');
	//mt19937_64 myrand(time(0));
	return 0;
}
posted @ 2025-07-08 11:51  Wy_x  阅读(33)  评论(4)    收藏  举报