zoukankan      html  css  js  c++  java
  • 【题解】动态逆序对 [CQOI2011] [P3157] [BZOJ3295] [P1393]

    【题解】动态逆序对 [CQOI2011] [P3157] [BZOJ3295] [P1393]


    水一水QAQ

    题目链接; ([P3157]) ([BZOJ3295])

    【题目描述】

    对于一个序列 (a) ,他的逆序对数定义为满足 (i<j),且 (a_i>a_j) 的数对 ((i,j)) 的个数。
    给出 (1)(n) 的一个排列,依次删除 (m) 个元素,在每次删除一个元素之前统计出序列的逆序对数。

    【输入】

    输入第一行包含两个整数 (n)(m),以下 (n) 行包含 (n)个小于等于 (n) 的正整数,即初始序列。随后 (m) 行共 (m) 个正整数,表示要删除的元素。

    【输出】

    输出包含 (m) 行,表示每次删除某个元素之前,逆序对的个数。

    【样例】

    输入:
    5 4
    1
    5
    3
    4
    2
    5
    1
    4
    2
    
    样例输出:
    5
    2
    2
    1
    
    样例解释:
    初始序列:(1,5,3,4,2)
    逆序对个数为:5,删去5后的序列:(1,3,4,2)
    逆序对个数为:2,删去1后的序列:(3,4,2)
    逆序对个数为:2,删去4后的序列:(3,2)
    逆序对个数为:1,删去2后的序列:(3)
    
    

    【数据范围】

    (100\%) (n leqslant 1e5,m leqslant 5e4)


    【分析】

    这道题的解法似乎比较多,但本蒟蒻只会一种【主席树】。

    首先考虑一下不带修改的静态求逆序对,直接用树状数组随便搞搞就行了:
    (a1[i]) 表示位于 (i) 前面大于 (a[i]) 的元素的个数,
    (a2[i]) 表示位于 (i) 后面小于 (a[i]) 的元素的个数,
    答案则是: (sum_{i=1}^n a1[i])

    带了修改后,问题变得略微麻烦了一点,首先按静态求逆序对的方法计算出初始序列的逆序对个数,每删除一个数 (a[i]),它所造成的影响就是:使序列中的逆序对个数减少了 (a1[i]+a2[i]) 个,于是只需要在删除某个数后计算一下贡献值即可。
    但如果 (a1[i])(a2[i]) 统计的数中有已经删除过的怎么办?那相当于是对于同一对逆序数减了两次(删除前面的数时已经减去了这一对逆序数,消去了它的贡献,删除后面的数时又减了一次),所以在计算删除 (a[i]) 的贡献时还需要加上在已经删除的数中可以与 (a[i]) 形成的逆序对数,可以用主席树维护已经删除的数。

    (【Code】)

    #pragma GCC optimize(2)
    #pragma GCC optimize(3,"Ofast","inline")
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #define mid (L+R>>1)
    #define Re register int
    #define LL long long
    #define F(i,a,b) for(Re i=a;i<=b;++i)
    using namespace std;
    const int N=1e5+3,M=5e4+3;LL ans;
    int x,n,T,fu,cnt,C[N],a[N],b[N],a1[N],a2[N],pt[N],ptl[50],ptr[50];
    struct QAQ{int g,lp,rp;}tree[N*50];//空间一定要开足 
    inline void in(int &x){
        x=fu=0;char c=getchar();
        while(c<'0'||c>'9')fu|=c=='-',c=getchar();
        while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=fu?-x:x;
    }
    inline void out(LL x){if(x>9)out(x/10);putchar(x%10+'0');}
    inline int ask(Re x){Re ans=0;while(x)ans+=C[x],x-=x&-x;return ans;}//树状数组的单点修改 
    inline void add(Re x){while(x<=n)++C[x],x+=x&-x;}//树状数组的区间查询 
    inline int ask_(Re L,Re R,Re x,Re rule){
        Re tl=0,tr=0,ans=0;
        for(Re i=L-1;i;i-=i&-i)ptl[++tl]=pt[i];
        for(Re i=R;i;i-=i&-i)ptr[++tr]=pt[i];
        L=1,R=n;
        while(L<R)
            if(x<=mid){//若x在左子树,则右子树全在x的后面 
            	if(rule){//查询已删除的数中在i后面且比a[i]小的数 
            		F(i,1,tl)ans-=tree[tree[ptl[i]].rp].g;//累加上右子树的所有信息 
            		F(i,1,tr)ans+=tree[tree[ptr[i]].rp].g;
                }
            	F(i,1,tl)ptl[i]=tree[ptl[i]].lp;
            	F(i,1,tr)ptr[i]=tree[ptr[i]].lp;
            	R=mid;//进入左子树的查询 
            }
            else{//若x在右子树,则左子树全在x的前面 
            	if(!rule){//查询已删除的数中在i前面且比a[i]大的数 
            		F(i,1,tl)ans-=tree[tree[ptl[i]].lp].g;//累加上右子树的所有信息 
            		F(i,1,tr)ans+=tree[tree[ptr[i]].lp].g;
                }
            	F(i,1,tl)ptl[i]=tree[ptl[i]].rp;
            	F(i,1,tr)ptr[i]=tree[ptr[i]].rp;
            	L=mid+1;//进入右子树的查询 
            }
        return ans;
    }
    inline void change(Re &p,Re L,Re R,Re x){//【单点修改】 
        if(!p)p=++cnt;++tree[p].g;
        if(L==R)return;
        if(x<=mid)change(tree[p].lp,L,mid,x);
        else change(tree[p].rp,mid+1,R,x);
    }
    int main(){
        in(n),in(T);
        F(i,1,n){//用树状数组预处理a1[] 
        	in(a[i]),b[a[i]]=i;
        	ans+=(a1[i]=ask(n)-ask(a[i]));//预处理出初始序列的逆序对数 
        	add(a[i]);
        }
        memset(C,0,sizeof(C));
        F(j,1,n){//用树状数组预处理a2[] 
        	Re i=n-j+1;
        	a2[i]=ask(a[i]-1);
        	add(a[i]);
        }
        while(T--){
        	out(ans);puts("");
        	in(x);x=b[x];//要删除 x ,b[x]为它原本的位置 
        	ans-=a1[x]+a2[x]-ask_(1,x-1,a[x],1)-ask_(x+1,n,a[x],0);
        	Re i=x;
            while(i<=n)change(pt[i],1,n,a[x]),i+=i&-i;//删除了数后更新一下线段树 
        }
    }
    

    双倍经验: ([P1393])

    这道题的数据范围变小了一些,但给定的序列需要离散化,并且题目改成了删除位于 (x) 上的数,注意一下这几个地方,随便搞搞就完事了。

    (【Code】)

    #pragma GCC optimize(2)
    #pragma GCC optimize(3,"Ofast","inline")
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #define mid (L+R>>1)
    #define Re register int
    #define LL long long
    #define F(i,a,b) for(Re i=a;i<=b;++i)
    using namespace std;
    const int N=4e4+3;LL ans;
    int x,n,m,T,fu,cnt,C[N],a[N],a1[N],a2[N],pt[N],pos[N],ptl[50],ptr[50];
    struct QAQ{int g,lp,rp;}tree[N*50];//空间一定要开足 
    struct QWQ{int i,x;inline bool operator<(QWQ b)const{return x<b.x;}}Q[N];
    inline void in(int &x){
        x=fu=0;char c=getchar();
        while(c<'0'||c>'9')fu|=c=='-',c=getchar();
        while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=fu?-x:x;
    }
    inline void out(LL x){if(x>9)out(x/10);putchar(x%10+'0');}
    inline int ask(Re x){Re ans=0;while(x)ans+=C[x],x-=x&-x;return ans;}//树状数组的单点修改 
    inline void add(Re x){while(x<=n)++C[x],x+=x&-x;}//树状数组的区间查询 
    inline int ask_(Re L,Re R,Re x,Re rule){
        Re tl=0,tr=0,ans=0;
        for(Re i=L-1;i;i-=i&-i)ptl[++tl]=pt[i];
        for(Re i=R;i;i-=i&-i)ptr[++tr]=pt[i];
        L=1,R=n;
        while(L<R)
            if(x<=mid){//若x在左子树,则右子树全在x的后面 
            	if(rule){//查询已删除的数中在i后面且比a[i]小的数 
            		F(i,1,tl)ans-=tree[tree[ptl[i]].rp].g;//累加上右子树的所有信息 
            		F(i,1,tr)ans+=tree[tree[ptr[i]].rp].g;
                }
            	F(i,1,tl)ptl[i]=tree[ptl[i]].lp;
            	F(i,1,tr)ptr[i]=tree[ptr[i]].lp;
            	R=mid;//进入左子树的查询 
            }
            else{//若x在右子树,则左子树全在x的前面 
            	if(!rule){//查询已删除的数中在i前面且比a[i]大的数 
            		F(i,1,tl)ans-=tree[tree[ptl[i]].lp].g;//累加上右子树的所有信息 
            		F(i,1,tr)ans+=tree[tree[ptr[i]].lp].g;
                }
            	F(i,1,tl)ptl[i]=tree[ptl[i]].rp;
            	F(i,1,tr)ptr[i]=tree[ptr[i]].rp;
            	L=mid+1;//进入右子树的查询 
            }
        return ans;
    }
    inline void change(Re &p,Re L,Re R,Re x){//【单点修改】 
        if(!p)p=++cnt;++tree[p].g;
        if(L==R)return;
        if(x<=mid)change(tree[p].lp,L,mid,x);
        else change(tree[p].rp,mid+1,R,x);
    }
    int main(){
        in(m),in(T);
        F(i,1,m)in(a[i]),Q[++n].x=a[i],Q[n].i=i;
        sort(Q+1,Q+n+1);
        F(i,1,n)pos[Q[i].i]=i;
    	F(i,1,m){//用树状数组预处理a1[] 
        	ans+=(a1[i]=ask(n)-ask(pos[i]));//预处理出初始序列的逆序对数 
        	add(pos[i]);
        }
        memset(C,0,sizeof(C));
        F(j,1,m){//用树状数组预处理a2[] 
        	Re i=m-j+1;
        	a2[i]=ask(pos[i]-1);
        	add(pos[i]);
        }
        out(ans),putchar(' ');
        while(T--){
        	in(x);//要删除 x 这个位置上的数
        	ans-=a1[x]+a2[x]-ask_(1,x-1,pos[x],1)-ask_(x+1,n,pos[x],0);
        	Re i=x;
            while(i<=n)change(pt[i],1,n,pos[x]),i+=i&-i;//删除了数后更新一下线段树 
            out(ans),putchar(' ');
        }
    }
    
  • 相关阅读:
    php遍历目录下的所有文件夹
    PHP 遍历文件
    PHP中public protected private的区别
    mysql数据库优化的方法
    Thinkphp 验证器
    PHP 接口
    php获取表单的值
    PHP 数组
    php 递归
    [go系列] 函数
  • 原文地址:https://www.cnblogs.com/Xing-Ling/p/10855105.html
Copyright © 2011-2022 走看看