CDQ分治
前言
这是一篇普通博客
这是近期学的最简单的东西了
终于学懂了,不容易啊QAQ
这个算法是\(\text{CDQ}\)巨佬发明的,所以得名CDQ分治
讲解
这次貌似没有喜闻乐见的百度百科自学了
CDQ分治用来解决点对的问题
例如:二维偏序,三维偏序
这里我们讲三维偏序(即板题)
这个东西是不是有点像逆序对(\(a_j<=a_i,b_j>b_i\))?只是又多了一层限制
根据逆序对的思想,我们先将其按照\(a\)从小到大排序
根据逆序对的思想,我们接下来考虑分治
对于区间\([l,mid],[mid+1,r]\)
我们分别将其按照\(b\)排序
此时这两个区间的\(b\)是有序的,右区间的任意一个\(a\)大于左区间的任意一个\(a\)
但是两个区间中各自的\(a\)并不是有序的
现在我们已经做到如果只看这两个区间,\(a_{\text{左区间}}\le a_{\text{右区间}},b_{\text{左区间}}\le b_{\text{右区间}}\)
也就是说,\(a,b\)都已经满足条件了
此时只考虑\(c\),只需要借助树状数组或线段树,用类似于求逆序对的思想即可求解
但是注意每次求解的时候要清空树状数组
记得去重 (我本来以为不用的,但是细品......)
练习
代码
板题代码
//12252024832524
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 100005;
const int MAXK = 200005;
int n,K,nn;
int t[MAXK],ans[MAXN];
struct node
{
int a,b,c,cnt,S;
}s[MAXN],dz[MAXN];
int Read()
{
int x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
void Put1(int x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
void Put(int x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x);
if(c >= 0) putchar(c);
}
template <typename T>T Max(T x,T y){return x > y ? x : y;}
template <typename T>T Min(T x,T y){return x < y ? x : y;}
template <typename T>T Abs(T x){return x < 0 ? -x : x;}
bool cmp1(node x,node y)
{
if(x.a != y.a) return x.a < y.a;
if(x.b != y.b) return x.b < y.b;
return x.c < y.c;
}
int lowbit(int x)
{
return x & (-x);
}
void Add(int x,int val)
{
for(int i = x;i <= K;i += lowbit(i)) t[i] += val;
}
int qsum(int x)
{
int ret = 0;
for(int i = x;i >= 1;i -= lowbit(i)) ret += t[i];
return ret;
}
void cdq(int l,int r)
{
if(l == r) return;
int mid = (l+r) >> 1;
cdq(l,mid);
cdq(mid+1,r);
for(int i = l;i <= r;++ i) dz[i] = s[i];
int j = l,i = mid+1,now = l;
while(j <= mid && i <= r)
{
if(dz[j].b <= dz[i].b) Add(dz[j].c,dz[j].S),s[now++] = dz[j++];
else dz[i].cnt += qsum(dz[i].c),s[now++] = dz[i++];
}
while(i <= r) dz[i].cnt += qsum(dz[i].c),s[now++] = dz[i++];
for(int fk = l;fk < j;++ fk) Add(dz[fk].c,-dz[fk].S);
while(j <= mid) s[now++] = dz[j++];
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n = Read();
K = Read();
for(int i = 1;i <= n;++ i)
{
s[i].a = Read();
s[i].b = Read();
s[i].c = Read();
s[i].S = 1;
}
sort(s+1,s+n+1,cmp1);
nn = 1;
for(int i = 2;i <= n;++ i)
if(s[i].a == s[i-1].a && s[i].b == s[i-1].b && s[i].c == s[i-1].c) s[nn].S ++;
else s[++nn] = s[i];
// for(int i = 1;i <= nn;++ i) printf("**%d %d %d %d\n",s[i].a,s[i].b,s[i].c,s[i].S);
// printf("nn : %d\n",nn);
cdq(1,nn);
for(int i = 1;i <= nn;++ i) ans[s[i].cnt+s[i].S-1] += s[i].S;
for(int i = 0;i < n;++ i) Put(ans[i],'\n');
return 0;
}
小优化
其实不用特别把\(b\)排序,因为我们在做cdq的时候,类似于归并排序(其实就是归并= =),我们直接就可以把\(b\)排了,往上的时候就不用了特别排序了
\(948ms → 340ms\),这就是优化的力量