归并排序和cdq分治

先上一个归并排序的代码:

#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排序,处理左边,右边;再处理左边对右边的影响,可以用树状数组实现。

P3810 【模板】三维偏序(陌上花开

题意:

输入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);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值