CodeForces 1096F Inversion Expection(树状数组 + DP + 组合计数)

 

 

好久没有做一道这么对胃口的题了……

大致题意:给出一个1..n的全排列,但是其中有些数字被替换成了-1。我们设给出数列中-1的个数为x,那么原来的全排列就有x!种,而且告诉你取这x!种每一种的概率相等,现在问你原排列的逆序对的期望是多少。

很显然,这个数列分为两个部分,一个部分是已经给出的正整数,另一部分是-1。那么相应的,逆序对可以分为三个部分,一个是正整数部分内部逆序对,一个是-1部分内部逆序对,最后是两个之间的逆序对。而这个二者之间的逆序对又可以分为,-1部分在前和正整数部分在前两种。

对于第一部分来说,相当于已经给出了所有数字直接去就一遍逆序对,这个是裸的求逆序对,各种NlogN的方法xjb搞即可,这里我用了树状数组。现在考虑第二部分,这x个-1内部的逆序对。这x个数字可能是离散的,但是逆序对只考虑大小关系而不考虑差值,所以可以看作这x个数字是1..x。那么问题就变成了1..x的所有排列,期望的逆序对数是多少。我们考虑dp[i]表示i个数字的期望逆序对数。那么显然有转移方程:

                  dp[i]=dp[i-1]+\frac{\sum_{j=0}^{i-1}1}{i}=dp[i-1]+\frac{i*(i-1)}{2*i}=dp[i-1]+\frac{i-1}{2}

进一步我们可以推出通项公式:

                                                                   dp[i]=\frac{i*(i-1)}{4}

所以第二部分完成。现在考虑第三部分,我们首先考虑正整数部分在前,也即只需考虑每一个正整数对其后面的-1产生的贡献。对于在位置i的正整数ai,它会对i之后的所有-1的位置产生影响。对于i之后的每一个-1的位置,产生的影响是所有未出现的正整数中小于ai的数字个数。我们设i之后为-1的位置的个数为pos,同时未出现过的比ai小的数字个数为t,那么这个ai产生的贡献就是:

                                                           \frac{pos*t*(x-1)!}{x!}=\frac{pos*t}{x}

如此统计所有正整数ai的贡献和即可完成这一部分。

对于-1在前的部分,直接做似乎不太好算。我们考虑仿照正整数在前的做法,还是以正整数为参考,但是却是反过来做。具体来说,对于在位置i的正整数ai,计算它与i之前所有-1的位置产生的贡献。与上面类似,对于每个ai都计算贡献,贡献和就是这一部分的答案。

最后把这三个部分,准确的说四个部分的答案加到一起就是我们要的结果。

至于复杂度的话,主要在第一部分求逆序对的NlogN和第三部分统计每个贡献的时候,需要logN确定未出现的正整数中比某个值大或者小的数字个数。因此总的复杂度为O(NlogN)。具体见代码:

#include <bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 200010;
const int mod = 998244353;
const int inv2 = (mod + 1) / 2;

int c[N],a[N],s[N],n,m;
LL ans,inv;

inline void update(int x,int y)
{
    for(int i=x;i<N;i+=i&-i)
        c[i]+=y;
}

inline int getsum(int x)
{
    int res=0;
    for(int i=x;i;i-=i&-i)
        res+=c[i];
    return res;
}

inline int qpow(int x,int n)
{
    int res=1;
    while(n)
    {
        if (n&1) res=(LL)res*x%mod;
        x=(LL)x*x%mod; n>>=1;
    }
    return res;
}

int main()
{
    ans=m=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        s[i]=s[i-1];
        scanf("%d",&a[i]);
        if (a[i]==-1) m++;
        else 
        {
            ans+=i-1-m-getsum(a[i]);
            update(a[i],1); s[i]++;
        }
    }
    ans%=mod; inv=qpow(m,mod-2);
    ans=(ans+(LL)m*(m-1)/2%mod*inv2%mod)%mod;
    for(int i=1;i<=n;i++)
    {
        if (a[i]==-1) continue;
        ans=(ans+(LL)(n-i-s[n]+s[i])*(a[i]-1-getsum(a[i]-1))%mod*inv%mod)%mod;
        ans=(ans+(LL)(i-1-s[i-1])*(n-a[i]-getsum(n)+getsum(a[i]))%mod*inv%mod)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值