[PKUSC2018]最大前缀和

[PKUSC2018]最大前缀和

题目大意:

\(n(n\le20)\)个数\(A_i(|A_i|\le10^9)\)。求这\(n\)个数在随机打乱后最大前缀和的期望值与\(n!\)的积在模\(998244353\)意义下的值。其中最大前缀和的定义为\(\forall i\in[1,n]\sum_{j=1}^iA_j\)的最大值。

思路:

考虑一个分界点\(p\),使得\(\sum A_{1\sim p}\)为最大前缀和,那么显然\(p\)之后的所有前缀和均\(<0\),否则就存在可以替换\(p\)的方案使得前缀和更大。

\(sum[i]\)表示子集\(i\)的数值和,\(f[i]\)表示最大前缀和为\(sum[i]\)的方案数,\(g[i]\)表示任意前缀和均为负的方案数。\(f[i]\)\(g[i]\)均可以通过动态规划求得。最后答案即为\(\sum sum[S]\times f[S]\times g[\overline S]\)。时间复杂度\(\mathcal O(2^nn)\)

源代码:

#include<cstdio>
#include<cctype>
typedef long long int64;
inline int getint() {
    register char ch;
    register bool neg=false;
    while(!isdigit(ch=getchar())) neg|=ch=='-';
    register int x=ch^'0';
    while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
    return neg?-x:x;
}
const int N=20,mod=998244353;
int a[N],f[1<<N],g[1<<N];
int64 sum[1<<N];
int main() {
    const int n=getint(),u=(1<<n)-1;
    g[0]=1;
    for(register int i=0;i<n;i++) {
        f[1<<i]=1;
        a[i]=getint();
        for(register int s=0;s<1<<n;s++) {
            if(s&(1<<i)) sum[s]+=a[i];
        }
    }
    for(register int s=1;s<1<<n;s++) {
        if(sum[s]>0) {
            for(register int i=0;i<n;i++) {
                if(!(s&(1<<i))) {
                    (f[s|(1<<i)]+=f[s])%=mod;
                }
            }
        } else {
            for(register int i=0;i<n;i++) {
                if(s&(1<<i)) {
                    (g[s]+=g[s^(1<<i)])%=mod;
                }
            }
        }
    }
    int ans=0;
    for(register int i=1;i<1<<n;i++) {
        (ans+=(int64)sum[i]*f[i]%mod*g[u^i]%mod)%=mod;
    }
    printf("%d\n",(ans+mod)%mod);
    return 0;
}
posted @ 2018-06-08 10:52 skylee03 阅读(...) 评论(...) 编辑 收藏