想说的话
其实我也不是很会,写这样一篇笔记送给未来的自己
-----------------------------------------------------------------------------(NOIP2020)前一天,(xxbbkk)
算法思想
(CDQ)分治是分治的一种,本着分而治之的思想,用一半来算另一半的贡献。可以解决偏序问题。(蒟蒻只会三维以下)。
例题一:逆序对问题
用树状数组或归并排序的思想就可以做,是一个二维偏序问题。用权值树状数组。
这里是归并的。
#include<bits/stdc++.h>
using namespace std;
int n,a[500010],ans,a1[500010],b1[500010];
inline void msort(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
msort(l,mid),msort(mid+1,r);
for(int i=l;i<=mid;i++) a1[i]=a[i];
for(int i=mid+1;i<=r;i++) b1[i]=a[i];
int tot=l-1,l1=l,l2=mid+1;
while(l1<=mid&&l2<=r){
if(a1[l1]>b1[l2]){
a[++tot]=b1[l2];
ans+=mid-l1+1;
l2++;
}
else{
a[++tot]=a1[l1];
l1++;
}
}
while(l1<=mid) a[++tot]=a1[l1++];
while(l2<=r) a[++tot]=b1[l2++];
return;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
msort(1,n);
printf("%d
",ans);
return 0;
}
这里是两种方法选其一,而三维偏序就是把他们结合起来。
例题二:三维偏序问题
先以(a)为第一关键字排序,解决掉一维。
向下分治时以(b)为第二关键字排序,再解决掉一维。在这里算的时候,我们只算前面那一半与后面那一半的组合产生的贡献,也就是跨越中点的贡献,这是分治的基本原则。
计算时用两个指针(l_1,l_2),(l_1)遍历前面的一半,(l_2)遍历后面的一半,此时我们保证了前边一半的(a)小于后面一半的(a),而两半内部的(b)是有序的,所以只要后一半的位置(l_2)的(b_{l_2})刚好大于前一半的某个位置(l_1)的(b_{l_1}),就可以把(l_1)之前所有的(c)的值加入权值树状数组中,在权值树状数组中查询(c_{l_2})的前缀和。
这里看代码清楚些。
inline void CDQ(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
CDQ(l,mid),CDQ(mid+1,r);
sort(v+l,v+mid+1,cmp2),sort(v+mid+1,v+r+1,cmp2);
int l1=l,l2;
for(l2=mid+1;l2<=r;l2++){
//移动指针l1,找到那个刚好大于的位置pos1,并在权值线段中插入遍历到的c
while(v[l1].b<=v[l2].b&&l1<=mid){
add(v[l1].c,v[l1].cnt);
l1++;
}
//累加答案,把权值树状数组中小于(此时的c_l2)的数的个数加上
v[l2].ans+=sum(v[l2].c);
}
//消除这一层的改变,还原树状数组
for(l2=l;l2<l1;l2++) add(v[l2].c,-v[l2].cnt);
return;
}
还有,如果有完全相同的一组(a,b,c), 就有可能算不到贡献。
所以我们要去重,统计个数。
code
#include<bits/stdc++.h>
#define N (100010)
using namespace std;
struct xbk{int a,b,c,cnt,ans;}x[N],v[N];
int n,m,k,c[200010],fa[200010];
inline int read(){
int w=0;
char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9'){
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w;
}
inline int lb(int x){return x&(-x);}
inline bool cmp1(xbk aa,xbk bb){
if(aa.a!=bb.a) return aa.a<bb.a;
else if(aa.b!=bb.b) return aa.b<bb.b;
else return aa.c<bb.c;
}
inline bool cmp2(xbk aa,xbk bb){
if(aa.b!=bb.b) return aa.b<bb.b;
else return aa.c<bb.c;
}
inline void add(int x,int g){
for(int i=x;i<=k;i+=lb(i)) c[i]+=g;
}
inline int sum(int x){
int res=0;
for(int i=x;i;i-=lb(i)) res+=c[i];
return res;
}
inline void pre(){
int now=0;
sort(x+1,x+1+n,cmp1);
for(int i=1;i<=n;i++){
now++;
if(x[i].a!=x[i+1].a||x[i].b!=x[i+1].b||x[i].c!=x[i+1].c){
v[++m].a=x[i].a;
v[m].b=x[i].b,v[m].c=x[i].c;
v[m].ans=0,v[m].cnt=now;
now=0;
}
}
return;
}
inline void CDQ(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
CDQ(l,mid),CDQ(mid+1,r);
sort(v+l,v+mid+1,cmp2),sort(v+mid+1,v+r+1,cmp2);
int l1=l,l2;
for(l2=mid+1;l2<=r;l2++){
while(v[l1].b<=v[l2].b&&l1<=mid){
add(v[l1].c,v[l1].cnt);
l1++;
}
v[l2].ans+=sum(v[l2].c);
}
for(l2=l;l2<l1;l2++) add(v[l2].c,-v[l2].cnt);
return;
}
int main(){
n=read(),k=read();
for(int i=1;i<=n;i++)
x[i].a=read(),x[i].b=read(),x[i].c=read();
pre();
CDQ(1,m);
for(int i=1;i<=m;i++) fa[v[i].ans+v[i].cnt-1]+=v[i].cnt;
for(int i=0;i<n;i++) printf("%d
",fa[i]);
return 0;
}
例题三:动态逆序对
待修改的逆序对,用权值作第一维(a),位置作第二维(b),结束时间做第三维(c)。
元素(y)对于元素(x)是逆序对的条件为:
(x.a<y.a)且(x.b > y.b)且(x.c>y.c)
或:
(x.a>x.b)且(x.b<y.b)且(x.c>y.c)
就变成两个三维偏序啦♪(∇*)
code
#include<bits/stdc++.h>
#define N (100010)
#define K (200010)
#define ll long long
using namespace std;
struct xbk{int a,b,c,cnt;ll ans;}x[N],v[N];
int n,m,kk,tot,cnt,now,c[K],re[N];
ll fi,ans[N];
inline int lb(int x){return x&(-x);}
inline int read(){
int w=0;
char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9'){
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w;
}
inline void add(int x,int kkk){
for(int i=x;i<=n;i+=lb(i)) c[i]+=kkk;
return;
}
inline ll sum(int x){
int res=0;
for(int i=x;i>0;i-=lb(i)) res+=c[i];
return res;
}
inline bool cmp1(xbk aa,xbk bb){
if(aa.a!=bb.a) return aa.a>bb.a;
else if(aa.b!=bb.b) return aa.b<bb.b;
else return aa.c<bb.c;
}
inline bool cmp2(xbk aa,xbk bb){
if(aa.b!=bb.b) return aa.b<bb.b;
else return aa.c<bb.c;
}
inline bool cmp3(xbk aa,xbk bb){
if(aa.b!=bb.b) return aa.b>bb.b;
else return aa.c<bb.c;
}
inline void CDQ(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
CDQ(l,mid),CDQ(mid+1,r);
sort(v+l,v+mid+1,cmp3),sort(v+mid+1,v+r+1,cmp3);
int i=l,j=mid+1;
for(j=mid+1;j<=r;j++){
while(v[i].b>v[j].b&&i<=mid){
add(v[i].c,v[i].cnt);
i++;
}
v[j].ans+=sum(v[j].c);
}
for(int k=l;k<i;k++) add(v[k].c,-v[k].cnt);
sort(v+l,v+mid+1,cmp2),sort(v+mid+1,v+r+1,cmp2);
i=l;
for(j=mid+1;j<=r;j++){
while(v[i].b<v[j].b&&i<=mid){
add(v[i].c,v[i].cnt);
i++;
}
v[j].ans+=sum(n)-sum(v[j].c);
}
for(int k=l;k<i;k++) add(v[k].c,-v[k].cnt);
return;
}
int main(){
n=read(),m=read();
kk=n;
for(int i=1;i<=n;i++){
v[i].b=read(),v[i].c=i;
re[v[i].b]=i;
v[i].a=1e9,v[i].cnt=1,v[i].ans=0;
}
for(int i=1;i<=m;i++){
int xx=read();
v[re[xx]].a=i;
}
sort(v+1,v+1+n,cmp1);
CDQ(1,n);
// for(int i=1;i<=n;i++){
// printf("%d %d %d %d
",v[i].a,v[i].b,v[i].c,v[i].ans);
// }
for(int i=1;i<=n;i++){
fi+=v[i].ans;
if(v[i].a!=1e9) ans[v[i].a]=v[i].ans;
}
for(int i=1;i<=m;i++) printf("%lld
",fi-=ans[i-1]);
return 0;
}