[loj6433]最大前缀和

考虑去枚举这个构成最大前缀和的集合$S$,那么限制分为两条:

1.前$|S|$个元素都属于$S$,且任意非空前缀和都小于$sum(S)$(其中$sum(S)$表示$S$中元素之和)(这里的小于是避免统计重复,强制要求最长的前缀)

2.后$n-|S|$个元素都不属于$S$,且最大前缀和为0(允许为空)

令$f(S)$表示$S$中的元素最大前缀和为0(允许为空)的方案数,这个的必要条件为$sum(S)\le 0$,同时在这样的条件下,对于序列最后一个数是无关的,因此$f(S)=\sum_{x\in S}f(S-x)$(这里$S-x$指去除$x$)

对于第一个条件,也就是最小非空后缀和大于0,将$a_{i}$变为其相反数后,也就是最大非空后缀和小于0,但特别的,这个后缀不能等于全集(因为本来的前缀非空)

先不考虑不能等于全集的条件,取反后用类似的方式求出$g(S)$(后缀和前缀是等价的),那么接下来再任意填上最后一个数即可,即$g(S)=\sum_{x\in S}g(S-x)$(类似背包的原理,要从后往前做)

由此即可$o(n2^{n})$求出答案

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define N 2000005
 4 #define mod 998244353
 5 int n,ans,sum[N],g[N],f[N];
 6 int main(){
 7     scanf("%d",&n);
 8     for(int i=0;i<n;i++)scanf("%d",&sum[1<<i]);
 9     g[0]=f[0]=1;//g[S]表示S中所有数最小前缀和为0 
10     for(int i=1;i<(1<<n);i++){
11         int k=(i&(i-1));
12         sum[i]=sum[k]+sum[i^k];
13         if (sum[i]>0){
14             for(int j=0;j<n;j++)
15                 if (i&(1<<j))g[i]=(g[i]+g[i^(1<<j)])%mod;
16         }
17         if (sum[i]<=0){
18             for(int j=0;j<n;j++)
19                 if (i&(1<<j))f[i]=(f[i]+f[i^(1<<j)])%mod;
20         }
21     }
22     for(int i=(1<<n)-1;i;i--)
23         if ((i&(i-1))==0)g[i]=1;
24         else{
25             g[i]=0;
26             for(int j=0;j<n;j++)
27                 if (i&(1<<j))g[i]=(g[i]+g[i^(1<<j)])%mod;
28         }
29     for(int i=0;i<(1<<n);i++)ans=(ans+1LL*(sum[i]+mod)*g[i]%mod*f[(1<<n)-1-i])%mod;
30     printf("%d",ans);
31 }
View Code

 

posted @ 2021-01-20 14:14  PYWBKTDA  阅读(139)  评论(0编辑  收藏  举报