bzoj 3262(cdq分治+树状数组)

本文详细解析了一道经典的三维偏序问题,采用cdq分治结合树状数组的方法,通过第一维排序、第二维分治及第三维动态维护,实现了高效求解。文章还讨论了函数开销对运行效率的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(正经题解在后面)斜体字都是一年前在没有把cdq扯清楚的情况下应付的,即使现在真正理解了cdq,还是将这堆话留在这,毕竟,花无重开日,人无再少年——

RunIDUserProblemResultMemoryTimeLanguageCode_LengthSubmit_Time
237469327116948973262Accepted6376 kb1632 msC++/Edit1851 B2017-10-23 19:52:20
237468727116948973262Accepted6376 kb3160 msC++/Edit1852 B2017-10-23 19:50:45

先说点题外话:一张图告诉你函数开销有多大,用重载的"<"运算符替换一个cmp函数可以省接近50%的时间!!!就两行的cmp函数都能带来如此大的代价orz。回头再看NOIP渐渐开始卡常数,以后只要思路清晰不影响排版,能不写函数就不写函数吧。。。

 

题解:

比较模板化的三维偏序。

第一维排序,第二维cdq分治,第三维树状数组。

每一次处理[l,mid]对[mid+1,r]的影响时,比较第二维,查询第三维(因为[l,mid]区间的x一定小于[mid+1,r]区间的x,第三维用树状数组维护满足条件的个数),当第二维符合要求时,将它加入树状数组中。每次更新[mid+1,r]区间的一个ans(记得处理完后还原树状数组)。

————————————————————————————————————————————————————————

正解:cdq分治+树状数组

先对第一维a排序,于是这一维后面可以先不管。

对第二维b进行cdq分治,将区间[l,r]二分成[l,mid]和[mid+1,r],用双指针扫左右区间,i扫左区间,j扫右区间。(最不好叙述的操作来了)用所有满足p[i].b<=p[j].b的i在树状数组p[i].c处进行add操作(加上i的出现次数,不一定是加1),然后用对应的p[j].c在树状数组中去查询(注意:所有add过的每次移动j指针前要减回去)。

双指针每扫一遍为O(n),把递归每一层都算上的话相当于扫了logn次,所以总复杂度为O(nlogn)。

问的是严格不大于自己的元素有i个,这样的元素有多少个(这种问法比较适合用定语从句来说233),所以cdq分治完后还有稍微转换一下得到答案f数组。

 

注意:必须先cdq函数中必须先递归处理左右子区间再处理当前区间。因为按照第二维b排序是会打乱原本第一维a的升序的!!!如果先处理当前区间,那么等你sort两下在操作一波再递归子区间时子区间已经不满足第一维a升序了。但是如果先处理子区间,无论你子区间那一层怎么排,当前层的第一维a仍然满足左区间小于右区间,这才可以放心大胆地去操作后两维。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+4;
int c[N<<1],n,nn=0,lim,f[N];
struct Node {
	int a,b,c,rep,ans;
	friend bool operator <(const Node &p,const Node &q) {//cmpb
		return p.b<q.b||(p.b==q.b&&p.c<q.c);
	}
	friend bool operator !=(const Node &p,const Node &q) {
		return p.a^q.a||p.b^q.b||p.c^q.c;
	}
}o[N],p[N];
inline bool cmpa(Node p,Node q) {
	if (p.a^q.a||p.b^q.b) return p.a<q.a||(p.a==q.a&&p.b<q.b);
	return p.c<q.c;
}
inline int read() {
	int x=0;char c=getchar();
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x;
}
inline void add(int x,int val) {
	for (int i=x;i<=lim;i+=i&-i) c[i]+=val;
}
inline int query(int x) {
	int ret=0;
	for (int i=x;i;i-=i&-i) ret+=c[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);
	sort(p+l,p+mid+1);//cmpb
	sort(p+mid+1,p+r+1);//cmpb
	int i=l,j=mid+1;
	while (j<=r) {
		while (i<=mid&&p[i].b<=p[j].b) add(p[i].c,p[i].rep),++i;
		p[j].ans+=query(p[j].c),++j;
	}
	for (int k=l;k<i;++k) add(p[k].c,-p[k].rep);
}
int main() {
//	freopen("bzoj 3262.in","r",stdin);
	n=read(),lim=read();
	for (register int i=1;i<=n;++i) o[i].a=read(),o[i].b=read(),o[i].c=read();
	sort(o+1,o+n+1,cmpa);
	int tim=0;
	for (register int i=1;i<=n;++i) {
		++tim;
		if (o[i]!=o[i+1]) p[++nn]=o[i],p[nn].rep=tim,tim=0;
	}
	cdq(1,nn);
	for (register int i=1;i<=nn;++i) f[p[i].ans+p[i].rep-1]+=p[i].rep;
	for (register int i=0;i<n;++i) printf("%d\n",f[i]);
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值