[CQOI2011] 动态逆序对之CDQ解法+洛谷1393CDQ解法

前言

第一次做这道题目,用的是主席树套树状数组的方法,思想非常清晰明了,就是用数据结构进行暴力维护统计,然鹅主席树确实不好打,再套个树状数组,Debug就更困难了。
主席树套树状数组讲解链接
今天学习了基本的CDQ思想及方法,忽然记起这道题目有CDQ的解法,于是做了做。
不得不说 CDQ真是又好码,跑的又快,然鹅推到过程却不像上一种解法那么好搞。

思路分析

要转化成CDQ的模型,首先得有时间序,我们把删除的顺序看做时间序,然后把删除看做倒着插入,越早插入的时间序越小。
然后把每个位置上的数看做一维,把位置看做一维。
样例变成了
t 1 2 3 4 5(时间序)

x 3 5 4 1 2(大小序)

y 3 2 4 1 5(位置序)

我们设一个数为 (t0,x0,y0)
我们求解时,贡献是在它插入之前右边多少比它大的数,左边多少比它数
1.t<t0,x<x0,y>=y02.t<t0,x>x0,y<=y0
然后为了把不等号搞的方向一致,转化为
1.t<t0,x<x0,y<=(ny0+1)2.t<t0,x<(nx0+1),y<=y0
这样就转化为三维偏序问题了,直接上CDQ统计答案即可。

注意事项

由于求的是逆序对总和,很明显要开long long,可不要忘了。
输出上一次删除的答案,就是最后一个删除不用输出啦。

题目链接

BZOJ
COGS
Luogu
Ac Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#define il inline
#define lowbit(x) x&(-x)
#define ll long long
using namespace std;
const int maxm=111000;
struct node{
    int x,y,z,id;
    ll ans;
};
node a[maxm],b[maxm];
int m,n,val[maxm],rk[maxm];
ll ans[maxm],sum[maxm];
il int read()
{
    int x=0,w=1;
    char ch=0;
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*w;
}
il void ins(int x,int w){for(int i=x;i<=maxm;i+=lowbit(i)) sum[i]+=(ll)w;}
il ll ask(int x){ll ans=0;for(int i=x;i;i-=lowbit(i)) ans+=sum[i];return ans;}
il bool same(node x1,node x2){return (x1.x==x2.x)&&(x1.y==x2.y)&&(x1.z==x2.z);}
il bool comp(node x1,node x2){return (x1.x<x2.x)||(x1.x==x2.x&&(x1.y<x2.y||(x1.y==x2.y&&x1.z<x2.z)));}
void CDQ(int l,int r)
{
    if(l>=r) return;
    int mid=(l+r)>>1;
    CDQ(l,mid),CDQ(mid+1,r);
    for(int i=l,j=l,p=mid+1;i<=r;i++) 
    {
        if(j<=mid&&(p>r||a[j].y<=a[p].y)) b[i]=a[j++];
        else b[i]=a[p++];
    }//归并排序,在这段区间中把y排好.
    for(int i=l;i<=r;i++) 
    {
        a[i]=b[i];
        if(a[i].id<=mid) ins(a[i].z,1);//插入小的 
        else a[i].ans+=ask(a[i].z);//大的查询 
    }
    for(int i=l;i<=r;i++) 
     if(a[i].id<=mid) ins(a[i].z,-1);//消除影响 
}
int main()
{
    //freopen("inverse.in","r",stdin);
    //freopen("inverse.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;i++) a[i].y=i,a[i].z=read();
    for(int i=1,w;i<=m;i++) w=read(),rk[w]=i; 
    int tot=m;
    for(int i=1;i<=n;i++) if(!rk[i]) rk[i]=++tot;
    for(int i=1;i<=n;i++) a[i].x=tot-rk[a[i].z]+1;
    sort(a+1,a+n+1,comp);
    for(int i=1;i<=n;i++) a[i].y=n-a[i].y+1,a[i].id=i;
    CDQ(1,n);
    for(int i=1;i<=n;i++) ans[a[i].x]+=a[i].ans;
    sort(a+1,a+n+1,comp);
    for(int i=1;i<=n;i++) a[i].y=n-a[i].y+1,a[i].z=n-a[i].z+1,a[i].ans=0,a[i].id=i;
    CDQ(1,n);
    for(int i=1;i<=n;i++) ans[a[i].x]+=a[i].ans;
    for(int i=1;i<=n;i++) ans[i]+=ans[i-1];
    for(int i=n;i>n-m;i--) printf("%lld\n",ans[i]);
    return 0; 
}

洛谷用户福利

有一道非常相似的题目
1393
连名字都一样。
但是这双倍经验可是不好得的。
1.这题目没有保证数据<=n(所以我们要离散化)
2.给出的是删除的数的位置,而不是数值。
3.先输出总的逆序对个数,然后输出每个删除后逆序对的个数。
Ac Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#define il inline
#define lowbit(x) x&(-x)
#define ll long long
using namespace std;
const int maxm=111000;
struct node{
    int x,y,z,id;
    ll ans;
};
node a[maxm],b[maxm];
int m,n,val[maxm],rk[maxm];
ll ans[maxm],sum[maxm];
il int read()
{
    int x=0,w=1;
    char ch=0;
    while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*w;
}
il void ins(int x,int w){for(int i=x;i<=maxm;i+=lowbit(i)) sum[i]+=(ll)w;}
il ll ask(int x){ll ans=0;for(int i=x;i;i-=lowbit(i)) ans+=sum[i];return ans;}
il bool same(node x1,node x2){return (x1.x==x2.x)&&(x1.y==x2.y)&&(x1.z==x2.z);}
il bool comp(node x1,node x2){return (x1.x<x2.x)||(x1.x==x2.x&&(x1.y<x2.y||(x1.y==x2.y&&x1.z<x2.z)));}
void CDQ(int l,int r)
{
    if(l>=r) return;
    int mid=(l+r)>>1;
    CDQ(l,mid),CDQ(mid+1,r);
    for(int i=l,j=l,p=mid+1;i<=r;i++) 
    {
        if(j<=mid&&(p>r||a[j].y<=a[p].y)) b[i]=a[j++];
        else b[i]=a[p++];
    }//归并排序,在这段区间中把y排好.
    for(int i=l;i<=r;i++) 
    {
        a[i]=b[i];
        if(a[i].id<=mid) ins(a[i].z,1);//插入小的 
        else a[i].ans+=ask(a[i].z);//大的查询 
    }
    for(int i=l;i<=r;i++) 
     if(a[i].id<=mid) ins(a[i].z,-1);//消除影响 
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++) a[i].y=i,a[i].z=read(),val[i]=a[i].z;
    sort(val+1,val+n+1);
    int t=unique(val+1,val+n+1)-val-1;
    for(int i=1;i<=n;i++) a[i].z=lower_bound(val+1,val+t+1,a[i].z)-val;
    for(int i=1,w;i<=m;i++) w=read(),rk[w]=i; 
    int tot=m;
    for(int i=1;i<=n;i++) if(!rk[i]) rk[i]=++tot;
    for(int i=1;i<=n;i++) a[i].x=tot-rk[i]+1;
    sort(a+1,a+n+1,comp);
    for(int i=1;i<=n;i++) a[i].y=n-a[i].y+1,a[i].id=i;
    CDQ(1,n);
    for(int i=1;i<=n;i++) ans[a[i].x]+=a[i].ans;
    sort(a+1,a+n+1,comp);
    for(int i=1;i<=n;i++) a[i].y=n-a[i].y+1,a[i].z=n-a[i].z+1,a[i].ans=0,a[i].id=i;
    CDQ(1,n);
    for(int i=1;i<=n;i++) ans[a[i].x]+=a[i].ans;
    for(int i=1;i<=n;i++) ans[i]+=ans[i-1];
    for(int i=n;i>=n-m;i--) printf("%lld ",ans[i]);
    return 0; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值