zoukankan      html  css  js  c++  java
  • 【agc023E】Inversions(线段树,动态规划)

    【agc023E】Inversions(线段树,动态规划)

    题面

    AT
    给定(a_i),求所有满足(p_ile a_i)的排列(p)的逆序对数之和。

    题解

    首先如何计算排列(p)的个数。
    (cnt[i])表示(a_kge i)的个数,那么满足条件的(p)的总数就是(prod cnt[i]-(n-i))
    大概就是从(n)开始填数,对于每个数字(i)而言,它一共有(cnt[i])个位置可以填,但是后面的数字一共占用了(n-i)个位置,所以还剩下(cnt[i]-(n-i))个位置可以填。

    先讲讲(O(n^2log))的做法。
    枚举任意两个位置(i,j,ilt j),考虑(i,j)之间形成逆序对的贡献。
    分成三种情况讨论。

    1.(a_i=a_j)
    显然对于任意一种满足条件的方案,要么(p_i>p_j)要么(p_i<p_j),并且两两对称,所以就是总方案除(2)
    2.(a_i<a_j)
    (p_j)([a_i+1,a_j])这部分的值的时候,显然不会产生贡献,而当(p_jin[1,a_i])时,则和上面是一样的,但是注意一下,此时我们直接把(a_j)变成了(a_i),会对于总方案产生影响,([a_i+1,a_j])这一段的(cnt)减小了,所以用一个线段树维护一下区间积,就可以方便的维护当前状态下的总方案,那么这一部分也很好算。
    3.(a_i>a_j)
    很麻烦,但是我们可以正难则反来算,用总方案减去(p_i<p_j)的方案数,转化成了第二种情况,也就是将(a_i)直接变成(a_j)

    这样子用线段树维护,时间复杂度(O(n^2logn)),用前后缀之类的东西维护可以做到(O(n^2))
    代码如下:

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<vector>
    using namespace std;
    #define ll long long
    #define MAX 200200
    #define MOD 1000000007
    #define inv2 500000004
    #define lson (now<<1)
    #define rson (now<<1|1)
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    int fpow(int a,int b)
    {
    	int s=1;
    	while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    	return s;
    }
    int n,a[MAX],tot,ans;
    int cnt[MAX];
    int t[2][MAX<<2];
    void Build(int now,int l,int r)
    {
    	if(l==r){t[1][now]=cnt[l]-1-(n-l);t[0][now]=cnt[l]-(n-l);return;}
    	int mid=(l+r)>>1;
    	Build(lson,l,mid);Build(rson,mid+1,r);
    	t[0][now]=1ll*t[0][lson]*t[0][rson]%MOD;
    	t[1][now]=1ll*t[1][lson]*t[1][rson]%MOD;
    }
    int Query(int now,int l,int r,int L,int R,int c)
    {
    	if(L<=l&&r<=R)return t[c][now];
    	int mid=(l+r)>>1,ret=1;
    	if(L<=mid)ret=1ll*ret*Query(lson,l,mid,L,R,c)%MOD;
    	if(R>mid)ret=1ll*ret*Query(rson,mid+1,r,L,R,c)%MOD;
    	return ret;
    }
    int main()
    {
    	n=read();tot=1;
    	for(int i=1;i<=n;++i)++cnt[a[i]=read()];
    	for(int i=n-1;i;--i)cnt[i]+=cnt[i+1];
    	for(int i=1;i<=n;++i)tot=1ll*tot*(cnt[i]-(n-i))%MOD;
    	if(!tot){puts("0");return 0;}
    	Build(1,1,n);
    	for(int i=1;i<=n;++i)
    		for(int j=i+1;j<=n;++j)
    		{
    			if(a[i]==a[j])ans=(ans+1ll*tot*inv2)%MOD;
    			else if(a[i]<a[j])
    			{
    				int d=1ll*tot*fpow(Query(1,1,n,a[i]+1,a[j],0),MOD-2)%MOD;
    				d=1ll*d*Query(1,1,n,a[i]+1,a[j],1)%MOD;
    				ans=(ans+1ll*d*inv2)%MOD;
    			}
    			else
    			{
    				int d=1ll*tot*fpow(Query(1,1,n,a[j]+1,a[i],0),MOD-2)%MOD;
    				d=1ll*d*Query(1,1,n,a[j]+1,a[i],1)%MOD;
    				ans=(ans+MOD-1ll*d*inv2%MOD+tot)%MOD;
    			}
    		}
    	printf("%d
    ",ans);
    	return 0;
    }
    
    

    而这种方法的复杂度瓶颈在于枚举任意两个位置之间逆序对的贡献。
    考虑这个能否优化。
    首先我们记(D_i=frac{cnt[i]-1-(n-i)}{cnt[i]-(n-i)}),如果要修改某个区间的总方案数,等价于用全局的总方案数乘上某一段区间。设(S)为全局总方案数,那么强制求改(a[i],a[j])成一样的总方案数就是:(S imes prod_{i=min+1}^{max}D_i)。这个东西很显然可以写成前缀的形式,也就是(S=frac{prod_{i=1}^{max}D_i}{prod_{i=1}^{min}D_i})
    这样子只需要维护前缀积然后求个和就好了。
    注意下(D_i)可能为(0),所以求的时候要分段计算一下贡献就好了,具体实现见代码。

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<vector>
    using namespace std;
    #define ll long long
    #define MAX 200200
    #define MOD 1000000007
    #define inv2 500000004
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    int fpow(int a,int b)
    {
    	int s=1;
    	while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    	return s;
    }
    int n,a[MAX],tot,ans;
    int cnt[MAX];
    int c1[MAX],c2[MAX];
    int lb(int x){return x&(-x);}
    void add(int x,int w){while(x<=n)c1[x]=(c1[x]+w)%MOD,++c2[x],x+=lb(x);}
    int getsum1(int x){int ret=0;while(x)ret=(ret+c1[x])%MOD,x-=lb(x);return ret;}
    int getsum2(int x){int ret=0;while(x)ret+=c2[x],x-=lb(x);return ret;}
    int D[MAX],invD[MAX],zero[MAX],S[MAX];
    int main()
    {
    	n=read();tot=1;
    	for(int i=1;i<=n;++i)++cnt[a[i]=read()];
    	for(int i=n-1;i;--i)cnt[i]+=cnt[i+1];
    	for(int i=1;i<=n;++i)tot=1ll*tot*(cnt[i]-=(n-i))%MOD;
    	if(!tot){puts("0");return 0;}
    	S[0]=D[0]=1;
    	for(int i=1;i<=n;++i)
    	{
    		int x=1ll*(cnt[i]-1)*fpow(cnt[i],MOD-2)%MOD;
    		if(!x)S[zero[i]=zero[i-1]+1]=i,D[i]=D[i-1];
    		else zero[i]=zero[i-1],D[i]=1ll*D[i-1]*x%MOD;
    		invD[i]=fpow(D[i],MOD-2);
    	}
    	for(int i=1;i<=n;++i)
    	{
    		ans=(ans+1ll*(getsum1(a[i])-getsum1(S[zero[a[i]]]-1)+MOD)*D[a[i]]%MOD*tot%MOD*inv2%MOD)%MOD;
    		add(a[i],invD[a[i]]);
    	}
    	for(int i=1;i<=n;++i)c1[i]=c2[i]=0;
    	for(int i=n;i;--i)
    	{
    		ans-=1ll*(getsum1(a[i]-1)-getsum1(S[zero[a[i]]]-1)+MOD)*D[a[i]]%MOD*tot%MOD*inv2%MOD;
    		ans=(ans+1ll*getsum2(a[i]-1)*tot)%MOD;ans=(ans+MOD)%MOD;
    		add(a[i],invD[a[i]]);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    
    
  • 相关阅读:
    Spring AOP应用场景你还不知道?这篇一定要看!
    解决 Failed to start LSB: Bring up/down networking 问题
    查出undefined symbol项命令
    将当前目录加入库环境变量
    Fortran代码生成so库
    Java调用Fortran生成so库报“libifport.so.5: 无法打开共享对象文件”错误解决方法
    HBase过滤器(转载)
    HBase设计规范(转载)
    spark(2.1.0) 操作hbase(1.0.2)
    zookeeper搭建
  • 原文地址:https://www.cnblogs.com/cjyyb/p/9567855.html
Copyright © 2011-2022 走看看