Educational Codeforces Round 57 1096F - Inversion Expectation(递推)

这道题如果用我的方法分解比较多,所以我注释了一下代码,方便大家看_(:з」∠)_.

A.问题转化

求所有可能的不同序列的逆序数期望.
很容易知道所有不同情况有 v a c ! vac! vac!种, v a c vac vac是空位置的个数,这样我们就直接求所有不同序列的逆序数之和就行了.

B.分三步求解

把已经填入的数叫做已知数,把没有填入的数叫未知数.
考虑三方面的因素:

1.已知数和已知数之间的贡献 a n s ans ans:

显然如果把未知数都从数列中拿走,这道题就变成了求数列逆序数.经典的做法是树状数组 O ( n l g n ) O(nlgn) O(nlgn).并且已知数和已知数对每个不同的数列贡献为 1 1 1.
a n s = v a c ! ∑ i = 1 n ∑ j = i + 1 n [ a [ i ] ≠ − 1 ] [ a [ j ] ≠ − 1 ] [ a [ i ] > a [ j ] ] ans=vac!\sum_{i=1}^n\sum_{j=i+1}^n[a[i]\neq -1][a[j]\neq -1][a[i]>a[j]] ans=vac!i=1nj=i+1n[a[i]̸=1][a[j]̸=1][a[i]>a[j]]

2.已知数和未知数之间的贡献 r e s res res:

对于每一个未知的空,他出现在了 ( k − 1 ) ! (k-1)! (k1)!个序列中,所以每个未知的空的贡献是 ( k − 1 ) ! (k-1)! (k1)!.

对于每一个未知的空,他填入i的贡献是后面小于i的已知数的个数和前面大于i的已知数的个数。但这样不好统计.

考虑每个已知数的贡献。现在我们知道已知数前面有 a a a个空,后面有 b b b个空.考虑这样一个转移:
s u f [ i ] = [ h s [ i ] ] b [ i ] + s u f [ i + 1 ] , p r e [ i ] = [ h s [ i ] ] a [ i ] + p r e [ i + 1 ] . suf[i]=[hs[i]]b[i]+suf[i+1],\\ pre[i]=[hs[i]]a[i]+pre[i+1]. suf[i]=[hs[i]]b[i]+suf[i+1],pre[i]=[hs[i]]a[i]+pre[i+1].
那么显然答案就是这两个数列求和.
r e s = ∑ i = 1 n ( 1 − [ h s [ i ] ] ) ( p r e [ i ] + s u f [ i ] ) . res=\sum_{i=1}^{n} (1-[hs[i]])(pre[i]+suf[i]). res=i=1n(1[hs[i]])(pre[i]+suf[i]).

3.未知数和未知数之间的贡献 r e c rec rec:

显然等于所有 v a c vac vac长度的不同排列的逆序数之和.
这里给出一个递推式,可以 O ( n ) O(n) O(n)算出所有 1... v a c 1...vac 1...vac长度的答案:
r e c [ i ] = r e c [ i − 1 ] ⋅ i + i ( i − 1 ) 2 ⋅ f a c [ i − 1 ] . rec[i]=rec[i-1]\cdot i+\frac{i(i-1)}{2}\cdot fac[i-1]. rec[i]=rec[i1]i+2i(i1)fac[i1].
题目答案等于:

o u t = a n s ⋅ v a c ! + r e s ⋅ ( v a c − 1 ) ! + r e c [ v a c ] v a c ! . out=\frac{ans\cdot vac!+res\cdot (vac-1)!+rec[vac]}{vac!}. out=vac!ansvac!+res(vac1)!+rec[vac].

#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#define lowbit(_x) ((_x)&-(_x))
using namespace std;
const long long mod=998244353;

//a:数组
int a[200005],n;
//hs:hs[i]对应数组中是否存在i
bool hs[200005];
//ask:每个已知数对未知数的贡献
//fac:阶乘
//rec:未知数和未知数之间的贡献
//bit:树状数组
long long ask[200005],fac[200005],rec[200005],bit[200005];
//快速幂
long long qp(long long a,long long b){
	long long r=1;
	while(b){
		if(b&1)r=r*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return r;
}
//树状数组
void add(int x){
    while(x<=n){
        bit[x]++;
        x+=lowbit(x);
    }
    return ;
}
long long sum(int x){
    long long r=0;
    while(x>0){
        r+=bit[x];
        x-=lowbit(x);
    }
    return r;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    //res:未知数和已知数之间的贡献
    //ans:已知数和已知数之间的贡献
    //pre:前缀变量
    //suf:后缀变量
    //vac:未知数个数统计
	long long res=0,ans=0,pre=0,suf=0,vac=0;
	rec[1]=0;
    fac[0]=fac[1]=1;
    for (int i=2;i<=2e5+3;i++){
        fac[i]=fac[i-1]*i%mod;
        rec[i]=rec[i-1]*i%mod+(((((long long)i-1)*i/2)%mod)*fac[i-1])%mod;
        rec[i]%=mod;
    }
    suf=0;
	for(int i=n;i>=1;i--){
		if(a[i]==-1)suf++,vac++;
		else ask[a[i]]=suf,hs[a[i]]=1;
	}
	suf=0;
	for(int i=n;i>=1;i--){
		if(!hs[i])res+=suf;
		if(res>=mod)res-=mod;
		suf+=ask[i];
		if(suf>=mod)suf-=mod;
	}
    pre=0;
    for(int i=1;i<=n;i++){
		if(a[i]==-1)pre++;
		else ask[a[i]]=pre;
	}
	pre=0;
	for(int i=1;i<=n;i++){
		if(!hs[i])res+=pre;
		if(res>=mod)res-=mod;
		pre+=ask[i];
		if(pre>=mod)pre-=mod;
	}
    for(int i=n;i>=1;i--){
        if(a[i]!=-1){
            ans=ans+sum(a[i]);
            add(a[i]);
        }
    }
    res=((res*fac[vac-1]%mod+rec[vac])%mod)*qp(fac[vac],mod-2)%mod+ans;
	printf("%I64d\n",res%mod);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值