bzoj3295[Cqoi2011]动态逆序对(cdq分治||可持久化线段树)

题目链接:点这里!!!


3295: [Cqoi2011]动态逆序对

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 3043   Solved: 967
[ Submit][ Status][ Discuss]

Description

对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

Input

输入第一行包含两个整数nm,即初始元素的个数和删除的元素个数。以下n行每行包含一个1到n之间的正整数,即初始排列。以下m行每行一个正整数,依次为每次删除的元素。
 

Output

 
输出包含m行,依次为删除每个元素之前,逆序对的个数。

Sample Input

5 4
1
5
3
4
2
5
1
4
2

Sample Output

5
2
2
1

样例解释
(1,5,3,4,2)(1,3,4,2)(3,4,2)(3,2)(3)。

HINT

N<=100000 M<=50000



做法1:cdq分治

1、首先我们看到求逆序对,很容易想到用树状数组去求逆序对,这里在有修改的情况下,我们一样用树状数组来做,只是再加上cdq分治。


2、首先我们倒过来做,把删除看成添加操作。比如例题:它的添加顺序是(数值):3(没有被删除的数先添加)、2、4、1、5。


3、然后我们来定义一个数组a,三个属性id(添加操作的顺序),val(数值的大小),pv(在序列里的位置)。


4、按照id来排序。 然后我们对val来分治,用pv来更新和求值。


5、注意要更新两次和求值两次,具体看代码,再叠加起来就可。


代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<sstream>
#include<algorithm>
#include<vector>
#include<bitset>
#include<set>
#include<queue>
#include<stack>
#include<map>
#include<cstdlib>
#include<cmath>
#define PI 2*asin(1.0)
#define LL long long
#define pb push_back
#define pa pair<int,int>
#define clr(a,b) memset(a,b,sizeof(a))
#define lson lr<<1,l,mid
#define rson lr<<1|1,mid+1,r
#define bug(x) printf("%d++++++++++++++++++++%d\n",x,x)
#define key_value ch[ch[root][1]][0]C:\Program Files\Git\bin
const int  MOD = 1E9+7;
const LL N = 1E5+15;
const int maxn = 1e4+1000;
const int letter = 130;
const int INF = 1e17;
const double pi=acos(-1.0);
const double eps=1e-8;
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,k,val[N],c[N],vis[N],pp[N];
LL ans[N];
vector<int>ps;
struct node{
    int id,val,pv;
}a[N],b[N];
bool cmp(node a,node b){
    return a.id<b.id;
}
int lowbit(int x){return x&(-x);}
void update(int i,int v){
    for(int x=i;x<=n;x+=lowbit(x)) pp[x]+=v;
}
int getsum(int i){
    int ans=0;
    for(int x=i;x;x-=lowbit(x)) ans+=pp[x];
    return ans;
}
void solve(int l,int r){
    if(l==r) return;
    int mid=(l+r)>>1;/// val
    for(int i=l;i<=r;i++){
        if(a[i].val<=mid) update(a[i].pv,1);
        else ans[a[i].id]+=1ll*(getsum(n)-getsum(a[i].pv));
    }
    for(int i=l;i<=r;i++) if(a[i].val<=mid) update(a[i].pv,-1);
    for(int i=l;i<=r;i++){
        if(a[i].val>mid) update(a[i].pv,1);
        else ans[a[i].id]+=1ll*getsum(a[i].pv-1);
    }
    for(int i=l;i<=r;i++) if(a[i].val>mid) update(a[i].pv,-1);
    int ll=l;
    for(int i=l;i<=r;i++) if(a[i].val<=mid) b[ll++]=a[i];
    for(int i=l;i<=r;i++) if(a[i].val>mid) b[ll++]=a[i];
    for(int i=l;i<=r;i++) a[i]=b[i];
    solve(l,mid);
    solve(mid+1,r);
}
int main(){
    ps.clear();
    int x;
    n=read(),k=read();
    for(int i=1;i<=n;i++){
        a[i].val=read(),a[i].pv=i;
        c[a[i].val]=i;
    }
    for(int i=1;i<=k;i++){
        x=read();
        vis[c[x]]=1;
        ps.pb(c[x]);
    }
    int cnt=0;
    for(int i=1;i<=n;i++){
        if(!vis[i]) a[i].id=++cnt;
    }
    for(int i=ps.size()-1;i>=0;i--) a[ps[i]].id=++cnt;
    sort(a+1,a+cnt+1,cmp);
    solve(1,n);
    for(int i=1;i<=n;i++){
        ans[i]+=ans[i-1];
    }
    for(int i=k;i>=1;i--){
        printf("%lld\n",ans[n-k+i]);
    }
    return 0;
}
/*
5 4
1 5 3 4 2
5 1 4 2
5 5
5 4 3 2 1
5 4 3 2 1
*/

做法2: 可持久化线段树


这道题做了一天,超了一天的内存,傻逼了。= =,然后优化一下就过了,一脸蒙逼。


1、注意一下数据范围:n<=100000 m<=50000。一开始做的时候我用的是n个数去维护可持久化线段树,各种超内存。


2、仔细想了一下我们有n-m个数没有变过,所以我们离线处理。其他m个点,插入用可持久化线段树即可。


#include<cstdio>
#include<cstring>
#include<iostream>
#include<sstream>
#include<algorithm>
#include<vector>
#include<bitset>
#include<set>
#include<queue>
#include<stack>
#include<map>
#include<cstdlib>
#include<cmath>
#define PI 2*asin(1.0)
#define LL long long
#define pb push_back
#define pa pair<int,int>
#define clr(a,b) memset(a,b,sizeof(a))
#define lson lr<<1,l,mid
#define rson lr<<1|1,mid+1,r
#define bug(x) printf("%d++++++++++++++++++++%d\n",x,x)
#define key_value ch[ch[root][1]][0]C:\Program Files\Git\bin
const int  MOD = 1E9+7;
const LL N = 1E5+15;
const int maxn = 1e4+1000;
const int letter = 130;
const int INF = 1e17;
const double pi=acos(-1.0);
const double eps=1e-8;
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,k,val[N],w[N],c[N],d[N];
int sum[N*100],ls[N*100],rs[N*100],root[N],siz,ps[N],vis[N];
int pl[50],pr[50],aa,bb;
int vl[N],vr[N];
vector<LL>pp;
int lowbit(int x){
    return x&(-x);
}
void update(int i,int val){
    for(int x=i;x<=n;x+=lowbit(x)) ps[x]+=val;
}
int getsum(int i){
    int ans=0;
    for(int x=i;x>0;x-=lowbit(x)) ans+=ps[x];
    return ans;
}
void insert(int ll,int rr,int x,int &y,int v,int add){
    y=++siz;
    sum[y]=sum[x]+add;
    ls[y]=ls[x],rs[y]=rs[x];
    if(ll==rr) return;
    int mid=(ll+rr)>>1;
    if(v<=mid) insert(ll,mid,ls[x],ls[y],v,add);
    else insert(mid+1,rr,rs[x],rs[y],v,add);
}
int query(int l,int r,int w){ ///1 n w
    int suml=0,sumr=0;
    if(l==r){
        for(int i=0;i<aa;i++) suml+=sum[pl[i]];
        for(int i=0;i<bb;i++) sumr+=sum[pr[i]];
        return sumr-suml;
    }
    int mid=(l+r)>>1;
    for(int i=0;i<aa;i++) suml+=sum[ls[pl[i]]];
    for(int i=0;i<bb;i++) sumr+=sum[ls[pr[i]]];
    if(w<=mid){
        for(int i=0;i<aa;i++) pl[i]=ls[pl[i]];
        for(int i=0;i<bb;i++) pr[i]=ls[pr[i]];
        return query(l,mid,w);
    }
    else {
        for(int i=0;i<aa;i++) pl[i]=rs[pl[i]];
        for(int i=0;i<bb;i++) pr[i]=rs[pr[i]];
        return sumr-suml+query(mid+1,r,w);
    }
}
void init(){
    pp.clear();
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        val[i]=read();
        w[val[i]]=i; ///at
    }
    for(int i=1;i<=k;i++){
        int x;
        x=read();
        d[i]=x;
        c[i]=w[x];
        vis[c[i]]=1;
    }
    LL ans=0;
    int cnt=0;
    for(int i=1;i<=n;i++){
        if(!vis[i]){ ///no
            update(val[i],1);
            ans+=1ll*(cnt-getsum(val[i]-1));
            cnt++;
        }
        else {  /// shanchu
            vl[i]=cnt-getsum(val[i]-1);
        }
    }
    cnt=0;
    clr(ps,0);
    for(int i=n;i>=1;i--){
        if(!vis[i]){
            update(val[i],1);
        }
        else {
            vr[i]=getsum(val[i]-1);
        }
    }
    clr(ps,0);
    for(int i=k;i>=1;i--){
        aa=bb=0;
        for(int j=c[i];j<=n;j+=lowbit(j)) insert(1,n,root[j],root[j],d[i],1);
        for(int j=c[i]-1;j;j-=lowbit(j)) pr[bb++]=root[j];
        update(c[i],1);
        ans+=1ll*((LL)getsum(c[i]-1)-(LL)query(1,n,d[i]-1)+vl[c[i]]);
        aa=bb=0;
        for(int j=c[i];j;j-=lowbit(j)) pl[aa++]=root[j];
        for(int j=n;j;j-=lowbit(j)) pr[bb++]=root[j];
        ans+=1ll*(vr[c[i]]+query(1,n,d[i]-1));
        pp.pb(ans);
    }
    for(int i=pp.size()-1;i>=0;i--) printf("%lld\n",pp[i]);
}
int main(){
   // freopen("input.txt","r",stdin);
   // freopen("out.txt","w",stdout);
    init();
    return 0;
}
/*
5 4
1 5 3 4 2
5 1 4 2
10 5
10 9 8 7 6 5 4 3 2 1
5 4 3 2 1
5 3
5 4 3 2 1
3 2 1
*/



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值