[agc23E]Inversions

Atcoder

description

给你\(n\)\(\{a_i\}\),你需要求所有满足\(p_i\le a_i\)\(1-n\)排列的逆序对个数之和模\(10^9+7\)
\(n \le 2\times10^5\)

sol

首先考虑一下所有满足要求的排列总数。记\(cnt_i\)表示有多少个\(a_k\ge i\),从大到小填数,方案数就是\[S=\prod_{i=1}^ncnt_i-(n-i)\]
考虑枚举两个位置\(i,j\),计算满足\(p_i>p_j\)的排列数。分两种情况讨论:
\(a_i\le a_j\):这时候如果你让\(a_j=a_i\)的话方案数也是一样的,所以就强制把\(a_j\)改成\(a_i\),计算满足条件的排列数。因为两个位置本质上没有区别,所以\(p_i>p_j\)的排列数会恰好是合法排列数的一半。而强制把\(a_j\)改小这个操作相当于是把连续一段的\(cnt_i\)减一,可以用某种方式来维护一下。
\(a_i>a_j\):总排列数减不合法,相当于是要求\(p_i<p_j\)的方案数。无非是把上面的\(i,j\)互换了而已,计算方法还是一样的。
考虑一下怎么把\(O(n^2)\)的枚举优化到\(O(n\log n)\)。记\(D_i=\frac{cnt_j-1-(n-j)}{cnt_j-(n-j)}\)。那么枚举一对\(i,j\),它们的贡献是\[S\times\prod_{k=a_i+1}^{a_j}D_k=S\times\frac{\prod_{k=1}^{a_j}D_k}{\prod_{k=1}^{a_i}D_k}\]
树状数组以\(a_i\)为下标,维护\(\frac{1}{\prod_{k=1}^{a_i}D_k}\)的前缀和即可。
但是这样有问题。因为\(D_i\)可能为\(0\)。我们可以对每个位置,找到它前面出现的第一个\(0\),显然这个\(0\)之前的所有\(i\)都不会有贡献了,而在这个\(0\)之后到\(j\)这一段一定没有\(0\),所以可以维护不含\(0\)\(D_i\)前缀积。
\(a_i>a_j\)同理,需要额外维护个数以便计算总方案减不合法。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 2e5+5;
const int mod = 1e9+7;
int n,a[N],cnt[N],S=1,z[N],st[N],D[N],ID[N],c1[N],c2[N];
int fastpow(int a,int b){
    int res=1;
    while (b) {if (b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
    return res;
}
void mdf(int k,int v){while(k<=n)c1[k]=(c1[k]+v)%mod,++c2[k],k+=k&-k;}
int qry1(int k){int s=0;while(k)s=(s+c1[k])%mod,k^=k&-k;return s;}
int qry2(int k){int s=0;while(k)s+=c2[k],k^=k&-k;return s;}
int main(){
    n=gi();
    for (int i=1;i<=n;++i) ++cnt[a[i]=gi()];
    for (int i=n;i>=1;--i) cnt[i]+=cnt[i+1];
    for (int i=1;i<=n;++i){
        cnt[i]-=n-i;
        if (cnt[i]<=0) return puts("0"),0;
        S=1ll*S*cnt[i]%mod;
    }
    st[0]=D[0]=1;
    for (int i=1;i<=n;++i){
        int x=1ll*(cnt[i]-1)*fastpow(cnt[i],mod-2)%mod;
        if (!x) st[z[i]=z[i-1]+1]=i,D[i]=D[i-1];
        else z[i]=z[i-1],D[i]=1ll*D[i-1]*x%mod;
        ID[i]=fastpow(D[i],mod-2);
    }
    int inv2=(mod+1)>>1,ans=0;
    for (int i=1;i<=n;++i){
        ans=(ans+1ll*(qry1(a[i])-qry1(st[z[a[i]]]-1)+mod)*D[a[i]]%mod*S%mod*inv2)%mod;
        mdf(a[i],ID[a[i]]);
    }
    memset(c1,0,sizeof(c1)),memset(c2,0,sizeof(c2));
    for (int i=n;i;--i){
        ans=(ans-1ll*(qry1(a[i]-1)-qry1(st[z[a[i]]]-1)+mod)*D[a[i]]%mod*S%mod*inv2%mod+mod)%mod;
        ans=(ans+1ll*qry2(a[i]-1)*S)%mod;
        mdf(a[i],ID[a[i]]);
    }
    printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/zhoushuyu/p/9563729.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值