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\),这就是优化的力量

posted @ 2020-07-28 17:05  皮皮刘  阅读(139)  评论(0编辑  收藏  举报