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

题目链接

又是一个紫色的模板题。(上一次我记得是后缀数组)。这题我记得当时大一时在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
*/
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值