P3810 【模板】三维偏序(陌上花开)——CDQ 分治

P3810 【模板】三维偏序(陌上花开)

思想

CDQ 分治的操作思想是消除维度。

思路

比如这道题,每个元素是一个三元组。

由于是无序的,所以我们可以考虑对 a a a 升序排序,这样以后进行其他操作时, a a a 已经是有序的,不需要再考虑。

而为了保证后续的 b b b 都是有序的,又不改变现在 a a a 的有序,所以可以考虑以 b b b 为基准进行归并排序。

回顾一下归并排序的过程:将数组分为左右两部分,左右依次排序,合并。

也就是说,在合并之前,左边的所有 a a a 一定小于等于右边的所有 a a a,因为原先的 a a a 都是有序的(这一点要想清楚)。

接着,我们在合并的同时遍历 b r b_r br 找到左边所有小于它的 b l b_l bl,由于归并排序, a a a 一定都满足条件。

现在已经消除了两个维度了,先算一下时间复杂度。

  • 排序: O ( n log ⁡ n ) O(n \log n) O(nlogn)

  • 归并排序: O ( n log ⁡ n ) O(n \log n) O(nlogn)

排序是为了归并排序的 a a a 合法打下基础,归并排序保证了 a , b a,b a,b 合法, c c c 合法需要建立在 a , b a,b a,b 合法的基础上,所以应该套在归并排序里面。

那么这个实现需要达到什么?

  • c c c 小于当前 c n o w c_{now} cnow 的元素,也就是说查找 c c c 的值为 1 ∼ c n o w − 1 1\sim c_{now}-1 1cnow1 的元素个数。
  • 可以实现单点的修改,增减每个值对应的元素个数。
  • 由于需要套在归并排序里面,所以时间复杂度需要做到 log ⁡ \log log 级别。

那么满足这几个条件的东西有什么?很明显:树状数组。

树状数组有一个特性,点 x x x 覆盖的不是一个点,一个 1 ∼ x 1\sim x 1x 的区间,所以 c c c 小于当前 c n o w c_{now} cnow 的元素就是 q u e r y ( c ) query(c) query(c)

实现

虽然说思路有了,但是实现起来细节上可能还会有点问题。

1. 去重

题目中没有保证元素不相同,所以进行去重,同时统计个数(两个完全相同的元素可以互相大于)。

n=read(),k=read();
for(int i=1;i<=n;i++){
	a[i].a=read(),a[i].b=read(),a[i].c=read();
	a[i].cnt=1;
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++){
	if(a[i].a==a[m].a&&a[i].b==a[m].b&&a[i].c==a[m].c)a[m].cnt++;
	else a[++m]=a[i];
}
2. 树状数组的修改

合并和修改可以同时进行,记得是加上 a [ i ] . c n t a[i].cnt a[i].cnt 而不是 1 1 1,完事了记得清空。

while(j<=r){
	while(i<=mid&&a[i].b<=a[j].b){
		update(a[i].c,a[i].cnt);
		b[q++]=a[i++];
	}
	a[j].ans+=query(a[j].c);
	b[q++]=a[j++];
}
for(int p=l;p<i;p++)update(a[p].c,-a[p].cnt);
3. 后面忘了

后面没了。

代码

#include<bits/stdc++.h>
#define lowbit(x) x&-x
#define endl putchar('\n')
using namespace std;
const int N=1e6+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
int n,m,k;
struct node{
	int a,b,c,cnt,ans;
}a[N],t[N],b[N];
int ans[N];
int bit[N];
bool cmp(node a,node b){
	return a.a<b.a||(a.a==b.a&&a.b<b.b)||(a.a==b.a&&a.b==b.b&&a.c<b.c);
}
void update(int x,int v){
	while(x<=k){
		bit[x]+=v;
		x+=lowbit(x);
	}
}
int query(int x){
	int res=0;
	while(x){
		res+=bit[x];
		x-=lowbit(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);
	int i=l,j=mid+1,q=l;
	while(j<=r){
		while(i<=mid&&a[i].b<=a[j].b){
			update(a[i].c,a[i].cnt);
			b[q++]=a[i++];
		}
		a[j].ans+=query(a[j].c);
		b[q++]=a[j++];
	}
	for(int p=l;p<i;p++)update(a[p].c,-a[p].cnt);
	while(i<=mid)b[q++]=a[i++];
	for(int p=l;p<=r;p++)a[p]=b[p];
}
signed main(){
	n=read(),k=read();
	for(int i=1;i<=n;i++){
		a[i].a=read(),a[i].b=read(),a[i].c=read();
		a[i].cnt=1;
	}
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++){
		if(a[i].a==a[m].a&&a[i].b==a[m].b&&a[i].c==a[m].c)a[m].cnt++;
		else a[++m]=a[i];
	}
	cdq(1,m);
	for(int i=1;i<=m;i++)ans[a[i].ans+a[i].cnt-1]+=a[i].cnt;
	for(int i=0;i<n;i++)print(ans[i]),endl;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值