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

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

RunID User Problem Result Memory Time Language Code_Length Submit_Time
2374693 2711694897 3262 Accepted 6376 kb 1632 ms C++/Edit 1851 B 2017-10-23 19:52:20
2374687 2711694897 3262 Accepted 6376 kb 3160 ms C++/Edit 1852 B 2017-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
#include
#include
#include
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'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

 

 

你可能感兴趣的:(数据结构)