1. 程式人生 > 實用技巧 >[loj6433]最大字首和

[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