欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

bzoj3262: 陌上花开/洛谷P3810 【模板】三维偏序(陌上花开)----cdq分治模板题

程序员文章站 2021-12-30 07:15:21
...

题目链接

又是一个紫色的模板题。(上一次我记得是后缀数组)。这题我记得当时大一时在upc上做到过,当时就没做出来。

可以先了解一下分治的来源:算法学习笔记(61): cdq分治,陈丹琦分治。
与其说分治是个算法,不如说这更偏向思维。

分治法所能解决的问题一般具有以下几个特征:

1) 该问题的规模缩小到一定的程度就可以容易地解决

2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。

3) 利用该问题分解出的子问题的解可以合并为该问题的解;

4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;

第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;、

第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。

第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。

上面这4个特性的讲解是转载的:链接


分析

网上很多题解就一句:三维偏序模板,一维排序(sort),二维分治(cdq), 三维树状数组(BIT)

我们先考虑只有两个元素的情况。
一、先排序(a小,再b小)
二、对于每个b,他影响之后b比他大的所有,类似BIT,影响[b,end]区间
这就是简单的二维偏序,普通的BIT或dp等方法即可解出。

回归三维偏序。分治的关键还是第三条特性:可以合并

利用这特性,之后考虑第二维时,因为a排序了,保证左区间所有的a都小于右区间所有的a,之后只用考虑b,c元素 ,回到二维偏序了。

看代码吧,可以自己debug一下。

#include<bits/stdc++.h>
using namespace std;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
const int N=2e5+7;
int n,m,cnt,t[N],ans[N];
struct node{
	int a,b,c,s,ans;
}p[N],rt[N],tmp[N];
//tmp结构体是暂存将要替代的rt结构体 
int lowbit(int i){	return i&(-i);	}
bool cmp(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;
}
void add(int i,int x){	for(;i<=m;i+=lowbit(i))	t[i]+=x;	}
int query(int i){
	int res=0;
	for(;i;i-=lowbit(i))	res+=t[i];
	return res;
}
void cdq(int l,int r){
	if(l==r){
		rt[l].ans+=rt[l].s-1;
		return;
	}
	int mid=l+r>>1;
	//二维分治 
	cdq(l,mid);	cdq(mid+1,r);
	//三维树状数组
	//因为a排序了,保证左区间所有的a都小于右区间所有的a,之后只用考虑b,c元素 
	int x=l,y=mid+1,tot=l;
//	cout<<x<<' '<<y<<' '<<r<<endl;
	while(x<=mid&&y<=r){
		if(rt[x].b<=rt[y].b){
			add(rt[x].c,rt[x].s);
			tmp[tot++]=rt[x++];
		}
		else{
			rt[y].ans+=query(rt[y].c);
			tmp[tot++]=rt[y++];
		}
	}
//	cout<<x<<' '<<y<<endl;
	//先查询右区间剩余值的影响情况 
	while(y<=r){
		rt[y].ans+=query(rt[y].c);
		tmp[tot++]=rt[y++];
	}
	//清空BIT 
	for(int i=l;i<x;i++)	add(rt[i].c,-rt[i].s);
	//将左区间剩余的加到后面 
	while(x<=mid)	tmp[tot++]=rt[x++];
	 
//	for(int i=l;i<=r;i++)	cout<<i<<' ';
//	cout<<endl;
//	for(int i=l;i<=r;i++)	cout<<rt[i].s<<' ';
//	cout<<endl;
//	for(int i=l;i<=r;i++)	cout<<rt[i].ans<<' ';
//	cout<<endl<<endl;
	
	for(int i=l;i<=r;i++)	rt[i]=tmp[i];
	//类似归并排序 
}

int main(){
	n=read();	m=read();
	for(int i=1;i<=n;i++){	p[i].a=read();	p[i].b=read();	p[i].c=read();	}
	sort(p+1,p+1+n,cmp);
	//一维排序
	//去重并合并 
	for(int i=1;i<=n;i++){
		if(p[i].a!=p[i-1].a||p[i].b!=p[i-1].b||p[i].c!=p[i-1].c)
			rt[++cnt]=p[i];
		rt[cnt].s++;
	}
	cdq(1,cnt);
	for(int i=1;i<=cnt;i++)	ans[rt[i].ans]+=rt[i].s;
	for(int i=0;i<n;i++)	printf("%d\n",ans[i]);
}
/*
10 3
3 3 3
2 3 3
2 3 1
3 1 1
3 1 2
1 3 1
1 1 2
1 2 2
1 3 2
1 2 1

3
1
3
0
1
0
1
0
0
1
*/
相关标签: 分治 分治算法