先上一个归并排序的代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+9;
int a[N],temp[N],n;
ll ans=0;
void merge_sort(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
merge_sort(l,mid),merge_sort(mid+1,r);
int p=l,x=l;
for(int i=mid+1;i<=r;i++){
while(p<=mid&&a[p]<=a[i])temp[x++]=a[p++];
temp[x++]=a[i];ans+=mid-p+1;
}
while(p<=mid)temp[x++]=a[p++];
for(int i=l;i<=r;i++)a[i]=temp[i];
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
merge_sort(1,n);
//for(int i=1;i<=n;i++)if(i==n)cout<<a[i]<<endl;else cout<<a[i]<<" ";
cout<<ans<<endl;
return 0;
}
这是一个求逆序对的归并排序,其思想就是cdq分治思想。
1、cdq一般含修改查询
2、分:和归并一样,把它分为左右两部分
3、合:与归并不一样,要考虑左边对右边的影响。
优点:常数小;缺点:必须离线。
主要用于降维。
二维偏序问题:
简单题,就是问对于(a,b),有多少个点对(a1,b1)(a1<a,b1<b)。
1.用树状数组就可以解,先按a排序,然后从前到后循环,到某一个点时,先查询比当前b小的有多少个,然后把当前b加入树状数组中,a就不用管了,因为已经有序了。其实按a排序后,就是一个逆序对问题。
2.考虑用cdq分治来解,其实就是和上面给的代码求逆序对差不多,只是贡献不同而已。
三维偏序问题:
给定N个有序三元组(a,b,c),求对于每个三元组(a,b,c),有多少个三元组(a2,b2,c2)满足a2<a且b2<b且c2<c。
1、可以用树套树做(我不会),是在线,但是常数大。
2、cdq分治:先按a排序,处理左边,右边;再处理左边对右边的影响,可以用树状数组实现。
题意:
输入n(1e5),k(2e5),表示元素数量和最大属性值。
接下来n行输入a,b,c表示三种属性。
f(i)表示aj<=ai&&bj<=bi&&cj<=ci,j的数量。
输出n行,第d+1行表示f(i)=d中i的数量。
题解:
典型三维偏序板题,但是要注意去重(因为重复的它的排名是一样的,不去重的话会不一样),最后加入答案。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<string>
#include<cstring>
#define lowbit(x) (x&-x)
using namespace std;
const int N=1e5+9;
const int M=2e5+9;
struct T{
int x,y,z,ans,cnt;
}a[N];
int b[M],rk[N],v[N];
int n,k;
bool cmp1(T t1,T t2){return t1.x!=t2.x?t1.x<t2.x:(t1.y!=t2.y?t1.y<t2.y:t1.z<t2.z);}
bool cmp2(T t1,T t2){return t1.y!=t2.y?t1.y<t2.y:t1.z<t2.z;}
inline void add(int x,int y){for(;x<=k;x+=lowbit(x))b[x]+=y;}
inline int query(int x){int ans=0;while(x)ans+=b[x],x-=lowbit(x);return ans;}
void cdq(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
sort(a+l,a+mid+1,cmp2),sort(a+mid+1,a+r+1,cmp2);
int j=l;
for(int i=mid+1;i<=r;i++){
while(j<=mid&&a[j].y<=a[i].y)add(a[j].z,a[j].cnt),++j;
a[i].ans+=query(a[i].z);
}
for(int i=l;i<j;i++)add(a[i].z,-a[i].cnt);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;++i)cin>>a[i].x>>a[i].y>>a[i].z;
sort(a+1,a+n+1,cmp1);
int nn=0,c=0;
for(int i=1;i<=n;i++){
c++;
if(a[i].x!=a[i+1].x||a[i].y!=a[i+1].y||a[i].z!=a[i+1].z)a[++nn]=a[i],a[nn].cnt=c,c=0;
}
cdq(1,nn);
for(int i=1;i<=nn;i++)v[a[i].ans+a[i].cnt-1]+=a[i].cnt;
for(int i=0;i<n;i++)cout<<v[i]<<"\n";
return 0;
}
Points and Rectangles(这里不用注意下一题的排序,因为它的时间,第一维就是递增的)
题意:
输入q(1e5),接下来q行;
每行输入 1,x,y 表示在平面上加一个点
或 2,x1,y1,x2,y2 表示在平面上加一个矩形
问对于每一个操作后,有多少个对点和矩形(点在矩形内)
题解:
经典偏序问题,按时间,横坐标,纵坐标形成三维偏序。
分两种情况:
1、矩形对答案的贡献,设矩形为(t,x1,y1,x2,y2),即算在小于t时间的sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1],
2、点对答案的贡献,设点为(x,y)与上面符号相反的求就好了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+9;
int q,na,x[N],y[N],nx,ny;
struct T{
int t,x,y,id,z;
}a[N];
ll ans[N];
int f[N];
void add(int x,int y){for(;x<N;x+=(x&-x))f[x]+=y;}
int query(int x){int ans=0;while(x)ans+=f[x],x-=(x&-x);return ans;}
inline int getidx(int z){return lower_bound(x+1,x+nx+1,z)-x;}
inline int getidy(int z){return lower_bound(y+1,y+ny+1,z)-y;}
bool cmp(T t1,T t2){if(t1.x!=t2.x)return t1.x<t2.x;return t1.y<t2.y;}
void cdq(int l,int r){
if(l>=r)return;
int mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
sort(a+l,a+mid+1,cmp),sort(a+mid+1,a+r+1,cmp);
int p=l;
for(int i=mid+1;i<=r;i++){
while(p<=mid&&a[p].x<=a[i].x){if(a[p].id==0)add(a[p].y,a[p].z);++p;}
if(a[i].id==1)ans[a[i].t]+=query(a[i].y)*a[i].z;
}
for(int i=l;i<p;i++)if(a[i].id==0)add(a[i].y,-a[i].z);
p=mid;
for(int i=r;i>=mid+1;i--){
while(p>=l&&a[p].x>=a[i].x){if(a[p].id==1)add(a[p].y,a[p].z);--p;}
if(a[i].id==0)ans[a[i].t]+=query(N-2)-query(a[i].y-1);
}
for(int i=mid;i>p;i--)if(a[i].id==1)add(a[i].y,-a[i].z);
}
int main(){
scanf("%d",&q);
int opt,x1,y1,x2,y2;
for(int i=1;i<=q;i++){
scanf("%d",&opt);
if(opt==1)scanf("%d%d",&x1,&y1),a[++na]=(T){i,x1,y1,0,1},x[++nx]=x1,y[++ny]=y1;
if(opt==2){
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
x[++nx]=x1-1,x[++nx]=x2,y[++ny]=y1-1,y[++ny]=y2;
a[++na]=(T){i,x1-1,y1-1,1,1};
a[++na]=(T){i,x1-1,y2,1,-1};
a[++na]=(T){i,x2,y1-1,1,-1};
a[++na]=(T){i,x2,y2,1,1};
}
}
sort(x+1,x+nx+1),sort(y+1,y+ny+1);
nx=unique(x+1,x+nx+1)-x-1,ny=unique(y+1,y+ny+1)-y-1;
for(int i=1;i<=na;i++)a[i].x=getidx(a[i].x),a[i].y=getidy(a[i].y);
cdq(1,na);
for(int i=1;i<=q;i++)ans[i]+=ans[i-1],printf("%lld\n",ans[i]);
return 0;
}
Twinkle(排序一定要注意 插入优先于查询,我因为这个调了一个半小时)
题意:
输入n(5e4),q(5e4),c(1e9);分别表示n颗星星,q个询问,星星最大亮度为c(c+1变0)
接下来n行输入x,y,s(s<=c),表示x,y处有一颗亮度为s的星星。
接下来输入q行;
每行输入t(t<=c),x1,y1,x2,y2;问过了t时间该矩形内星星的亮度和为多少。
题解:
由于s+t<=2*c,所以最多减一次c就可,先把矩形内星星亮度取和,加上t的贡献,最后减去多少个超过c的贡献。
现在要求该矩形内星星个数和星星亮度大于c-t的个数。
于是就变成了一个求(横坐标,纵坐标,星星亮度)的三维偏序问题;(注意星星尽量放前面)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+9;
int n,q,c,na,nx;
int f[N],x[N],tim[N];
void add(int x,int y){for(;x<N;x+=(x&-x))f[x]+=y;}
int query(int x){int ans=0;while(x)ans+=f[x],x-=(x&-x);return ans;}
ll ans[N],gs[N],les[N];
struct T{int x,y,s,z,id;}a[N];
bool cmp1(T t1,T t2){if(t1.x!=t2.x)return t1.x<t2.x;return t1.id<t2.id;}/***很重要的一步 插入优先于查询***/
bool cmp2(T t1,T t2){if(t1.y!=t2.y)return t1.y<t2.y;return t1.id<t2.id;}
inline int getidx(int y){return lower_bound(x+1,x+nx+1,y)-x;}
void cdq(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
sort(a+l,a+mid+1,cmp2),sort(a+mid+1,a+r+1,cmp2);
int p=l,js=0; ll he=0;
for(int i=mid+1;i<=r;i++){
while(p<=mid&&a[p].y<=a[i].y){if(a[p].id==0)add(a[p].s,1),js++,he+=x[a[p].s];p++;}
if(a[i].id)gs[a[i].id]+=js*a[i].z,ans[a[i].id]+=he*a[i].z,les[a[i].id]+=query(a[i].s)*a[i].z;
}
for(int i=l;i<p;i++)if(a[i].id==0)add(a[i].s,-1);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>q>>c;
for(int i=1,xx,y,s;i<=n;i++)cin>>xx>>y>>s,a[++na]=(T){xx,y,s,1,0},x[++nx]=s;
for(int i=1,t,x1,y1,x2,y2;i<=q;i++){
cin>>t>>x1>>y1>>x2>>y2;tim[i]=t;
a[++na]=(T){x1-1,y1-1,c-t,1,i};
a[++na]=(T){x1-1,y2,c-t,-1,i};
a[++na]=(T){x2,y1-1,c-t,-1,i};
a[++na]=(T){x2,y2,c-t,1,i};
x[++nx]=c-t;
}
sort(x+1,x+nx+1);nx=unique(x+1,x+nx+1)-x-1;
for(int i=1;i<=na;i++)a[i].s=getidx(a[i].s);
sort(a+1,a+na+1,cmp1);
cdq(1,na);
for(int i=1;i<=q;i++)cout<<ans[i]+1ll*gs[i]*tim[i]-1ll*(gs[i]-les[i])*(c+1)<<endl;
return 0;
}
四维偏序问题:
四维偏序裸题思路,CDQ套CDQ即a通过flag标记,在CDQ2的时候,b已经是有序的了,所以只用再c归并,d树状数组求和。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=5e4+5;
inline int read(){
char c=getchar();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int n;
struct Operation{
int a,b,c,d;
bool flag;
}a[N],t1[N],t2[N];
int c[N];
inline int lowbit(int x){return x&-x;}
inline void add(int p,int v){for(;p<=n;p+=lowbit(p)) c[p]+=v;}
inline int sum(int p){
int re=0;
for(;p;p-=lowbit(p)) re+=c[p];
return re;
}
int ans;
void CDQ2(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
CDQ2(l,mid);CDQ2(mid+1,r);
int i=l,j=mid+1,p=l;
Operation *a=t1,*t=t2;
while(i<=mid||j<=r){
if(j>r||(i<=mid&&a[i].c<a[j].c)){
if(a[i].flag) add(a[i].d,1);
t[p++]=a[i++];
}else{
if(!a[j].flag) ans+=sum(a[j].d);
t[p++]=a[j++];
}
}
for(int i=l;i<=mid;i++) if(a[i].flag) add(a[i].d,-1);
for(int i=l;i<=r;i++) a[i]=t[i];
}
void CDQ(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
CDQ(l,mid);CDQ(mid+1,r);
int i=l,j=mid+1,p=l;
Operation *t=t1;
while(i<=mid||j<=r){
if(j>r||(i<=mid&&a[i].b<a[j].b)) (t[p++]=a[i++]).flag=1;
else (t[p++]=a[j++]).flag=0;
}
for(int i=l;i<=r;i++) a[i]=t[i];
CDQ2(l,r);
}
int main(){
freopen("partial_order.in","r",stdin);
freopen("partial_order.out","w",stdout);
n=read();
for(int i=1;i<=n;i++) a[i].b=read();
for(int i=1;i<=n;i++) a[i].c=read();
for(int i=1;i<=n;i++) a[i].d=read(),a[i].a=i;
CDQ(1,n);
printf("%d",ans);
}