zoukankan      html  css  js  c++  java
  • 动态逆序对专练

    动态逆序对专练

    就是三倍经验

    题意

    维护一个序列,每次修改后求出当前序列逆序对个数。

    思路

    题目让我们求出

    [sum_{i=1}^nsum_{j=i+1}^n[a_i>a_j] ]

    也就是让我们求出满足

    [pos_i<pos_j&&a_i>a_j ]

    的点对数量。

    对于不修改的情况,这显然是一个三维偏序问题,用树状数组或归并处理都可以。

    对于修改的情况,我们可以用CDQ分治离线解决,或者使用树套树在线处理。

    我这么懒当然是用树套树啦~

    树状数组套值域线段树

    思想

    树状数组维护序列,值域线段树维护值域。

    优点是可以写成非递归式查询,常数相对较小。

    缺点是即使是动态开点空间消耗仍然很大。

    实现

    具体实现是每个树状数组节点开一棵线段树。修改时修改所有树状数组上包括的线段树,查询时类似。

    void update(int &ro,int l,int r,int x,int k){//l和r是当前值域区间,x为位置
        if(!ro) ro=++tot;
        val[ro]+=k;
        if(l==r)return;
        int mid=l+r>>1;
        if(x<=mid)update(ls[ro],l,mid,x,k);
        else update(rs[ro],mid+1,r,x,k);
    }
    

    墙裂安利非递归式线段树查询

    inline long long query(int l,int r,int x,int type){
        int cnta=0,cntb=0;
        long long ans=0;
        for(int i=l-1;i;i-=i&-i) qa[++cnta]=rt[i];//树状数组查询方法:差分
        for(int i=r;i;i-=i&-i)   qb[++cntb]=rt[i];//先将所有要处理的树状数组上的线段树全部记录,然后一起查询
        l=1,r=n;
        while(l<r){
            int mid=l+r>>1;
            if(x>mid){
                if(type){//type表示查询的类型,按照正常线段树查询的思路处理即可。这里type=1表示查左边
                    for(int i=1;i<=cnta;i++) ans-=val[ls[qa[i]]];
                    for(int i=1;i<=cntb;i++) ans+=val[ls[qb[i]]];
                }
                for(int i=1;i<=cnta;i++) qa[i]=rs[qa[i]];
                for(int i=1;i<=cntb;i++) qb[i]=rs[qb[i]];
                l=mid+1;
            }else{
                if(!type){
                    for(int i=1;i<=cnta;i++) ans-=val[rs[qa[i]]];
                    for(int i=1;i<=cntb;i++) ans+=val[rs[qb[i]]];
                }
                for(int i=1;i<=cnta;i++) qa[i]=ls[qa[i]];
                for(int i=1;i<=cntb;i++) qb[i]=ls[qb[i]];
                r=mid;
            }
        }
        return ans;
    }
    

    P3157 [CQOI2011]动态逆序对

    给你一个排列,每次删除一个位置上的数,求每次操作后的逆序对数。

    思路

    插入原序列之后得到原序列答案,每次删除一个数 (x) 查询 (pos_i<x)(a_i>a_x)(pos_i>x)(a_i<a_x) 的答案并用之前的答案减去,然后删除它的影响。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cctype>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    inline int read(){
    	int w=0,x=0;char c=getchar();
    	while(!isdigit(c))w|=c=='-',c=getchar();
    	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
    	return w?-x:x;
    }
    namespace star
    {
    	const int maxn=1e5+10,maxm=3e7+10;
    	int n,m,a[maxn],ls[maxm],rs[maxm],tot,rt[maxn],val[maxm];
    	void update(int &ro,int l,int r,int x,int k){
    		if(!ro) ro=++tot;
    		val[ro]+=k;
    		if(l==r)return;
    		int mid=(l+r)>>1;
    		if(x<=mid) update(ls[ro],l,mid,x,k);
    		else update(rs[ro],mid+1,r,x,k);
    	}
    	int qa[maxn],qb[maxn];
    	long long ans;
    	inline long long query(int l,int r,int x,int type){
    		int cnta=0,cntb=0;
    		long long ans=0;
    		for(int i=l-1;i;i-=i&-i) qa[++cnta]=rt[i];
    		for(int i=r;i;i-=i&-i)   qb[++cntb]=rt[i];
    		l=1,r=n;
    		while(l<r){
    			int mid=(l+r)>>1;
    			if(x>mid){
    				if(type){
    					for(int i=1;i<=cnta;i++)ans-=val[ls[qa[i]]];
    					for(int i=1;i<=cntb;i++)ans+=val[ls[qb[i]]];
    				}
    				for(int i=1;i<=cnta;i++)qa[i]=rs[qa[i]];
    				for(int i=1;i<=cntb;i++)qb[i]=rs[qb[i]];
    				l=mid+1;
    			}else{
    				if(!type){
    					for(int i=1;i<=cnta;i++)ans-=val[rs[qa[i]]];
    					for(int i=1;i<=cntb;i++)ans+=val[rs[qb[i]]];
    				}
    				for(int i=1;i<=cnta;i++)qa[i]=ls[qa[i]];
    				for(int i=1;i<=cntb;i++)qb[i]=ls[qb[i]];
    				r=mid;
    			}
    		}
    		return ans;
    	}
    	int pos[maxn];
    	inline void work(){
    		n=read(),m=read();
    		for(int i=1;i<=n;i++) {
    			ans+=query(1,i-1,a[i]=read(),0);pos[a[i]]=i;
    			for(int x=i;x<=n;x+=x&-x) update(rt[x],1,n,a[i],1);
    		}
    		printf("%lld
    ",ans);
    		while(--m){
    			int x=read();
    			ans-=query(1,pos[x]-1,x,0)+query(pos[x]+1,n,x,1);
    			printf("%lld
    ",ans);
    			for(int j=pos[x];j<=n;j+=j&-j) update(rt[j],1,n,x,-1);
    		}
    	}
    }
    signed main(){
    	star::work();
    	return 0;
    }
    

    CF785E Anton and Permutation

    给你一个原序列为递增排列的序列,每次交换两个位置上的数,求每次操作后的逆序对数。

    思路

    相对于上一题,并非删除而是交换两个位置上的数,实际上就是在原位置删除两个数然后在彼此的位置又加上这两个数。

    这里我先减去影响然后更新再加上影响,最后单独讨论一下这两个数之间互换对答案的贡献。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cctype>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    inline int read(){
    	int w=0,x=0;char c=getchar();
    	while(!isdigit(c))w|=c=='-',c=getchar();
    	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
    	return w?-x:x;
    }
    namespace star
    {
    	const int maxn=2e5+1,maxm=3e7+1;
    	int n,Q,a[maxn],rt[maxn];
    	long long ans;
    	int ls[maxm],rs[maxm],tot,val[maxm];
    	void update(int &ro,int l,int r,int x,int k){
    		if(!ro) ro=++tot;
    		val[ro]+=k;
    		if(l==r)return;
    		int mid=l+r>>1;
    		if(x<=mid)update(ls[ro],l,mid,x,k);
    		else update(rs[ro],mid+1,r,x,k);
    	}
    	int qa[maxn],qb[maxn];
    	inline long long query(int l,int r,int x,int type){
    		int cnta=0,cntb=0;
    		long long ans=0;
    		for(int i=l-1;i;i-=i&-i) qa[++cnta]=rt[i];
    		for(int i=r;i;i-=i&-i)   qb[++cntb]=rt[i];
    		l=1,r=n;
    		while(l<r){
    			int mid=l+r>>1;
    			if(x>mid){
    				if(type){
    					for(int i=1;i<=cnta;i++) ans-=val[ls[qa[i]]];
    					for(int i=1;i<=cntb;i++) ans+=val[ls[qb[i]]];
    				}
    				for(int i=1;i<=cnta;i++) qa[i]=rs[qa[i]];
    				for(int i=1;i<=cntb;i++) qb[i]=rs[qb[i]];
    				l=mid+1;
    			}else{
    				if(!type){
    					for(int i=1;i<=cnta;i++) ans-=val[rs[qa[i]]];
    					for(int i=1;i<=cntb;i++) ans+=val[rs[qb[i]]];
    				}
    				for(int i=1;i<=cnta;i++) qa[i]=ls[qa[i]];
    				for(int i=1;i<=cntb;i++) qb[i]=ls[qb[i]];
    				r=mid;
    			}
    		}
    		return ans;
    	}
    	inline void work(){
    		n=read(),Q=read();
    		for(int i=1;i<=n;i++){
    			a[i]=i;
    			for(int j=i;j<=n;j+=j&-j) update(rt[j],1,n,a[i],1);
    		}
    		while(Q--){
    			int x=read(),y=read();
    			if(x==y){
    				printf("%lld
    ",ans);continue;
    			}
    			if(x>y)swap(x,y);
    			ans=ans-query(1,x-1,a[x],0)-query(x+1,n,a[x],1)-query(1,y-1,a[y],0)-query(y+1,n,a[y],1);
    			for(int i=x;i<=n;i+=i&-i) update(rt[i],1,n,a[x],-1),update(rt[i],1,n,a[y],1);
    			for(int i=y;i<=n;i+=i&-i) update(rt[i],1,n,a[x],1),update(rt[i],1,n,a[y],-1);
    			swap(a[x],a[y]);
    			ans=ans+query(1,x-1,a[x],0)+query(x+1,n,a[x],1)+query(1,y-1,a[y],0)+query(y+1,n,a[y],1);
    			ans+=(a[x]<a[y]?1:-1);
    			printf("%lld
    ",ans);
    		}
    	}
    }
    signed main(){
    	star::work();
    	return 0;
    }
    

    P1975 [国家集训队]排队

    给你一个序列,每次交换两个位置上的数,求每次操作后逆序对数。

    思路

    给上面的代码加个离散化XD

    后面单独讨论两个数的贡献时有一点区别。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cctype>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    inline int read(){
    	int w=0,x=0;char c=getchar();
    	while(!isdigit(c))w|=c=='-',c=getchar();
    	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
    	return w?-x:x;
    }
    namespace star
    {
    	const int maxn=2e4+1,maxm=3e6+1;
    	int n,Q,a[maxn],cnt,b[maxn],rt[maxn];
    	long long ans;
    	int ls[maxm],rs[maxm],tot,val[maxm];
    	void update(int &ro,int l,int r,int x,int k){
    		if(!ro) ro=++tot;
    		val[ro]+=k;
    		if(l==r)return;
    		int mid=l+r>>1;
    		if(x<=mid)update(ls[ro],l,mid,x,k);
    		else update(rs[ro],mid+1,r,x,k);
    	}
    	int qa[maxn],qb[maxn];
    	inline long long query(int l,int r,int x,int type){
    		int cnta=0,cntb=0;
    		long long ans=0;
    		for(int i=l-1;i;i-=i&-i) qa[++cnta]=rt[i];
    		for(int i=r;i;i-=i&-i)   qb[++cntb]=rt[i];
    		l=1,r=n;
    		while(l<r){
    			int mid=l+r>>1;
    			if(x>mid){
    				if(type){
    					for(int i=1;i<=cnta;i++) ans-=val[ls[qa[i]]];
    					for(int i=1;i<=cntb;i++) ans+=val[ls[qb[i]]];
    				}
    				for(int i=1;i<=cnta;i++) qa[i]=rs[qa[i]];
    				for(int i=1;i<=cntb;i++) qb[i]=rs[qb[i]];
    				l=mid+1;
    			}else{
    				if(!type){
    					for(int i=1;i<=cnta;i++) ans-=val[rs[qa[i]]];
    					for(int i=1;i<=cntb;i++) ans+=val[rs[qb[i]]];
    				}
    				for(int i=1;i<=cnta;i++) qa[i]=ls[qa[i]];
    				for(int i=1;i<=cntb;i++) qb[i]=ls[qb[i]];
    				r=mid;
    			}
    		}
    		return ans;
    	}
    	inline void work(){
    		n=read();
    		for(int i=1;i<=n;i++) a[i]=b[i]=read();
    		sort(b+1,b+1+n);
    		cnt=unique(b+1,b+1+n)-b-1;
    		for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+1+cnt,a[i])-b;
    		for(int i=1;i<=n;i++){
    			ans+=query(1,i-1,a[i],0);
    			for(int j=i;j<=n;j+=j&-j) update(rt[j],1,n,a[i],1);
    		}
    		printf("%lld
    ",ans);
    		Q=read();
    		while(Q--){
    			int x=read(),y=read();
    			if(x==y){
    				printf("%lld
    ",ans);continue;
    			}
    			if(x>y)swap(x,y);
    			ans=ans-query(1,x-1,a[x],0)-query(x+1,n,a[x],1)-query(1,y-1,a[y],0)-query(y+1,n,a[y],1);
    			for(int i=x;i<=n;i+=i&-i) update(rt[i],1,n,a[x],-1),update(rt[i],1,n,a[y],1);
    			for(int i=y;i<=n;i+=i&-i) update(rt[i],1,n,a[x],1),update(rt[i],1,n,a[y],-1);
    			swap(a[x],a[y]);
    			ans=ans+query(1,x-1,a[x],0)+query(x+1,n,a[x],1)+query(1,y-1,a[y],0)+query(y+1,n,a[y],1);
    			if(a[x]<a[y]) ans+=1;
    			else if(a[x]>a[y]) ans-=1;
    			printf("%lld
    ",ans);
    		}
    	}
    }
    signed main(){
    	star::work();
    	return 0;
    }
    

    复制了三遍代码显得很长的雅子

    对于后两个题更简单并且更优秀的分块解法,我不会因为没有普适性所以我们不学,嗯嗯。

  • 相关阅读:
    关键词提取算法TextRank
    我的博文目录整理
    Windows Azure一些小技巧集合
    js数组和树互转
    this.props.form.validateFields回调不执行问题
    d3的4.x与3.x版本的区别
    d3提示框,虚线,选择区域
    d3布局
    d3文件导入和导出
    d3交互
  • 原文地址:https://www.cnblogs.com/BrotherHood/p/13913039.html
Copyright © 2011-2022 走看看