cdq分治题


离线
左半部分内部的变化对右半部分没有影响


P3810 三维偏序
题意:

有n个元素,每个元素有a,b,c三种属性,属性的最大值为k
设f(i)为满足a(i)>=a(j),b(i)>=b(j),c(i)>=c(j)的j的数量

对于d属于[0,n),求f(i)=d的 i 的数量

数据范围:n<=1e5,a,b,c,k<=2e5

解法:

先对第一维排序消除第一维影响,然后开始分治,
分治过程中对左右两边按第二维排序,虽然排序之后第一维乱了,但是左半部分的第一维都小于右半部分,
因此只计算左右两部分的偏序关系,第一维乱掉是不会有影响的,计算过程用双指针+树状数组。
详见代码。

同时这题需要去重+合并相同的元素,否则答案会错。

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e5+5;
struct BIT{
    int c[maxm];
    int lowbit(int i){
        return i&-i;
    }
    void add(int i,int t){
        while(i<maxm)c[i]+=t,i+=lowbit(i);
    }
    int ask(int i){
        int ans=0;
        while(i)ans+=c[i],i-=lowbit(i);
        return ans;
    }
}T;
struct Node{
    int a,b,c,cnt,ans;
}a[maxm];
int ans[maxm];
int n,k;
bool cmp1(Node a,Node b){
    if(a.a!=b.a)return a.a<b.a;
    if(a.b!=b.b)return a.b<b.b;
    return a.c<b.c;
}
bool cmp2(Node a,Node b){
    return a.b<b.b;
}
void cdq(int l,int r){
    if(l==r)return ;
    int mid=(l+r)/2;
    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[i].b>=a[j].b){
            T.add(a[j].c,a[j].cnt);
            j++;
        }
        a[i].ans+=T.ask(a[i].c);
    }
    for(int i=l;i<j;i++){//清空树状数组
        T.add(a[i].c,-a[i].cnt);
    }
}
signed main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i].a>>a[i].b>>a[i].c;
    }
    //去重+合并
    sort(a+1,a+1+n,cmp1);
    int cnt=0;
    int num=0;
    for(int i=1;i<=n;i++){
        cnt++;
        if(i==n||a[i].a!=a[i+1].a||a[i].b!=a[i+1].b||a[i].c!=a[i+1].c){
            a[++num]={a[i].a,a[i].b,a[i].c,cnt,cnt-1};//当前节点的答案为cnt-1
            cnt=0;
        }
    }
    //cdq分治
    cdq(1,num);
    //
    for(int i=1;i<=num;i++){
        ans[a[i].ans]+=a[i].cnt;
    }
    for(int i=0;i<n;i++){
        cout<<ans[i]<<endl;
    }
    return 0;
}

P3157 [CQOI2011]动态逆序对

题意:

给定长度为n的排列,按照某种顺序删除m个元素,
要求计算每次删除之前,整个序列中的逆序对数量

数据范围:n<=1e5,m<=5e4

解法:

先计算出初始逆序对,然后每次计算减少的逆序对数量即可

记录每个元素的权值v,位置pos,删除时间time(不删除的元素time设置为m+1)
对于每一个被删除的元素,减少的逆序对数量为满足下列条件的元素数量:
1.在这个元素前面(pos),权值比他大(v),删除时间比他晚(time)
2.在这个元素后面(pos),权值比他小(v),删除时间比他晚(time)
这是一个三维偏序问题,那么可以用cdq分治解决。

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e5+5;
struct BIT{
    int c[maxm];
    int lowbit(int i){
        return i&-i;
    }
    void add(int i,int t){
        while(i<maxm)c[i]+=t,i+=lowbit(i);
    }
    int ask(int i){
        int ans=0;
        while(i)ans+=c[i],i-=lowbit(i);
        return ans;
    }
}T;
struct Node{
    int v,pos,time,ans=0;
    //权值,位置,删除时间
}a[maxm];
int pos[maxm];
int rev[maxm];
int n,m;
bool cmp1(Node a,Node b){//这题用不到,因为初始情况pos已经有序
    return a.pos<b.pos;
}
bool cmp2(Node a,Node b){
    return a.v>b.v;
}
void cdq(int l,int r){
    if(l==r)return ;
    int mid=(l+r)/2;
    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].v>a[i].v){
            T.add(a[j].time,1);
            j++;
        }
        a[i].ans+=(j-l)-T.ask(a[i].time);
    }
    for(int i=l;i<j;i++){//清空树状数组
        T.add(a[i].time,-1);
    }
    //
    j=r;
    for(int i=mid;i>=l;i--){
        while(j>=mid+1&&a[j].v<a[i].v){
            T.add(a[j].time,1);
            j--;
        }
        a[i].ans+=(r-j)-T.ask(a[i].time);
    }
    for(int i=r;i>j;i--){//清空树状数组
        T.add(a[i].time,-1);
    }
}
signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i].v;
        a[i].pos=i;
        a[i].time=m+1;
        pos[a[i].v]=i;
    }
    //计算初始逆序对
    int ans=0;
    for(int i=1;i<=n;i++){
        T.add(a[i].v,1);
        ans+=i-T.ask(a[i].v);
    }
    for(int i=1;i<=n;i++){//清空树状数组
        T.add(a[i].v,-1);
    }
    //
    for(int i=1;i<=m;i++){
        int x;cin>>x;
        a[pos[x]].time=i;
    }
    //初始情况pos已经有序了
    cdq(1,n);
    //
    for(int i=1;i<=n;i++){
        rev[a[i].time]+=a[i].ans;
    }
    for(int i=1;i<=m;i++){
        cout<<ans<<endl;
        ans-=rev[i];
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值