题目链接
又是一个紫色的模板题。(上一次我记得是后缀数组)。这题我记得当时大一时在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
*/