cdq分治
cdq 分治
适用范围
cdq 分治是一种用来解决特殊点对的数量或找到一对点对使得函数值最大的问题。
流程
首先找到序列中点 \(mid\),再将此区间分为三类:
- \(l \leq i \leq mid,l \leq j \leq r\);
- \(l \leq i \leq mid,mid+1 \leq j \leq r\);
- \(mid+1 \leq i \leq r,mid+1 \leq j \leq r\);
然后我们会发现,如果对 \([l,mid]\) 与 \([mid+1,r]\) 两个区间分治,那么第 \(1\) 种情况就会全部出现在 \([l,mid]\),而第 \(3\) 种情况会全部出现在区间 \([mid+1,r]\)。所以我们每次操作只需要考虑第 \(2\) 种操作。
模板
下面看到 Luogu P3810 【模板】三维偏序(陌上花开)。
步骤
开结构体储存。
struct node
{
int a,b,c;//三个值
int cnt;//重复的点的个数
int ans;//满足条件的数的个数(没有计算重复的点)
}x[N]/*输入*/,cdq[N]/*去重后的x*/;
首先对以 \(a\) 为第一关键字进行排序,此时就满足了第一维限制,然后进行去重操作,再进入 cdq 分治。
sort(x+1,x+n+1,cmp1);//解决第一维限制
int len=0,cnt=0;
rep1(i,1,n)
{
++cnt;//多少个相同的
if(x[i].a!=x[i+1].a||x[i].b!=x[i+1].b||x[i].c!=x[i+1].c)//不是一样的
{
cdq[++len]=x[i];//加入cdq数组
cdq[len].cnt=cnt;
cnt=0;
}
}
solve(1,len);//cdq分治
cdq 分治里面,首先判断区间左右端点是否相等,找到中间点,分治左右区间,递归完后再对进行左右区间部分分别以 \(b\) 为关键字排序,然后进行双指针。因为对 \(a\) 进行了排序,所以 \(\forall i,j,j<i,a_j<a_i\),而左边区间的下标一定小于右边区间的下标,所以 \(i=mid+1,j=l\)。接着在循环里面判断,只要此时 \(cdq_i\) 的 \(b\) 值大于 \(cdq_j\) 的 \(b\) 值。则使用树状数组维护动态区间和,最后不要忘记清空。
void solve(int l,int r)//cdq分治
{
if(l==r) return;//结束递归
int mid=l+r>>1;//序列的中间
solve(l,mid);//左边区间,包含所有1<=i<=mid&&1<=j<=mid
solve(mid+1,r);//右边区间,包含所有mid+1<=i<=r&&mid+1<=j<=r
sort(cdq+l,cdq+mid+1,cmp2);//左边区间的排序
sort(cdq+mid+1,cdq+r+1,cmp2);//右边区间的排序
int i=mid+1,j=l;//双指针
while(i<=r)
{
while(cdq[i].b>=cdq[j].b&&j<=mid/*没有超过区间*/)//满足第二维限制
{
add(cdq[j].c,cdq[j].cnt);//树状数组维护区间和
++j;
}
cdq[i].ans+=query(cdq[i].c);//记录答案
++i;
}
rep1(i,l,j-1) add(cdq[i].c,-cdq[i].cnt);//清空
return;
}
输出部分需要将我们所记录的答案形式进行转换,循环从 \(1\) 到 \(n\),要求的答案数组的下标即当前的答案加上重复点的个数减去自己本身即减 \(1\)。
rep1(i,1,len) ans[cdq[i].ans+cdq[i].cnt-1/*要加上重复的个数又因为i!=j所以要排除自己*/]+=cdq[i].cnt;//转换为输出要求的形式
rep1(i,0,n-1) cout<<ans[i]<<endl; //输出
完整代码
AC Code of Luogu P3810 【模板】三维偏序(陌上花开)。
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define x first
#define y second
#define rep1(i,l,r) for(int i=l;i<=r;i++)
#define rep2(i,l,r) for(int i=l;i>=r;i--)
#define debug() puts("----------")
const int N=2e5+10;
const int inf=0x3f3f3f3f3f3f3f3f;
const double pi=acos(-1);
using namespace std;
typedef complex<int> ci;
typedef complex<double> cd;
int n,k,tr[N],ans[N];
struct node
{
int a,b,c;//三个值
int cnt;//重复的点的个数
int ans;//满足条件的数的个数(没有计算重复的点)
}x[N]/*输入*/,cdq[N]/*去重后的x*/;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f*x;
}
bool cmp1(node a,node b)//以a为关键字排序
{
if(a.a==b.a)
{
if(a.b==b.b) return a.c<b.c;
return a.b<b.b;
}
return a.a<b.a;
}
bool cmp2(node a,node b)//以b为关键字
{
if(a.b==b.b) return a.c<b.c;
return a.b<b.b;
}
//以下是树状数组
int lowbit(int x){return x&-x;}
void add(int i,int sum)
{
while(i<=k)
{
tr[i]+=sum;
i+=lowbit(i);
}
return;
}
int query(int i)
{
int ans=0;
while(i)
{
ans+=tr[i];
i-=lowbit(i);
}
return ans;
}
//以上是树状数组
void solve(int l,int r)//cdq分治
{
if(l==r) return;//结束递归
int mid=l+r>>1;//序列的中间
solve(l,mid);//左边区间,包含所有1<=i<=mid&&1<=j<=mid
solve(mid+1,r);//右边区间,包含所有mid+1<=i<=r&&mid+1<=j<=r
sort(cdq+l,cdq+mid+1,cmp2);//左边区间的排序
sort(cdq+mid+1,cdq+r+1,cmp2);//右边区间的排序
int i=mid+1,j=l;//双指针
while(i<=r)
{
while(cdq[i].b>=cdq[j].b&&j<=mid/*没有超过区间*/)//满足第二维限制
{
add(cdq[j].c,cdq[j].cnt);//树状数组维护区间和
++j;
}
cdq[i].ans+=query(cdq[i].c);//记录答案
++i;
}
rep1(i,l,j-1) add(cdq[i].c,-cdq[i].cnt);//清空
return;
}
signed main()
{
// #ifndef ONLINE_JUDGE
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// #endif
n=read();
k=read();
rep1(i,1,n)
{
x[i].a=read();
x[i].b=read();
x[i].c=read();
}
sort(x+1,x+n+1,cmp1);//解决第一维限制
int len=0,cnt=0;
rep1(i,1,n)
{
++cnt;//多少个相同的
if(x[i].a!=x[i+1].a||x[i].b!=x[i+1].b||x[i].c!=x[i+1].c)//不是一样的
{
cdq[++len]=x[i];//加入cdq数组
cdq[len].cnt=cnt;
cnt=0;
}
}
solve(1,len);//cdq分治
rep1(i,1,len) ans[cdq[i].ans+cdq[i].cnt-1/*要加上重复的个数又因为i!=j所以要排除自己*/]+=cdq[i].cnt;//转换为输出要求的形式
rep1(i,0,n-1) cout<<ans[i]<<endl; //输出
return 0;
}

浙公网安备 33010602011771号