1. 程式人生 > >BZOJ5369:[PKUSC2018]最大字首和(狀壓DP)

BZOJ5369:[PKUSC2018]最大字首和(狀壓DP)

Description

小C是一個演算法競賽愛好者,有一天小C遇到了一個非常難的問題:求一個序列的最大子段和。 但是小C並不會做這個題,於是小C決定把序列隨機打亂,然後取序列的最大字首和作為答案。 小C是一個非常有自知之明的人,他知道自己的演算法完全不對,所以並不關心正確率,他只關心求出的解的期望值, 現在請你幫他解決這個問題,由於答案可能非常複雜,所以你只需要輸出答案乘上n!後對998244353取模的值,顯然這是個整數。 注:最大字首和的定義:i∈[1,n],Sigma(a j)的最大值,其中1<=j<=i

Input

第一行一個正整數nnn,表示序列長度。 第二行n個數,表示原序列a[1..n],第i個數表示a[i]。 1≤n≤20,Sigma(|Ai|)<=10^9,其中1<=i<=N

Output

輸出一個非負整數,表示答案。

Sample Input

2
-1 2

Sample Output

3

Solution

首先對於一個序列$[a_1,a_n]$,設最大字首和的位置為$p$,那麼序列$[a_{p+1},a_n]$的任意一個字首必須都$<=0$。否則的話你用最大字首和隨便加上$[a_{p+1},a_n]$中$>0$的一個字首就可以得到新的最大字首和。

預處理:

$sum[S]$表示集合$S$的數字和。

$f[S]$表示欽定集合$S$當最大字首的合法方案數。

$g[S]$表示集合$S$任意字首和$<=0$小於$0$的方案數。

那麼顯然$ans=\sum sum[S]\times f[S]\times g[S']$。其中$S'$是$S$的補集。

$sum$和$g$都是可以直接求的,那麼$f$呢?

可以發現,如果$sum[S]>0$,那麼把隨便一個數放到這個集合$S$的最前面,這個最大字首和仍然是可以保證合法的。

$ans$最後忘了取模$WA$了好幾發……心態崩了

Code

 1 #include<iostream>
 2
#include<cstdio> 3 #define N (21) 4 #define MOD (998244353) 5 using namespace std; 6 7 int n,m,a[N],sum[1<<N],cnt[1<<N],f[1<<N],g[1<<N]; 8 9 int main() 10 { 11 scanf("%d",&n); m=(1<<n)-1; 12 for (int i=1; i<=n; ++i) scanf("%d",&a[i]); 13 for (int i=1; i<=n; ++i) 14 for (int S=0; S<=m; ++S) 15 if (S&(1<<i-1)) sum[S]+=a[i], cnt[S]++; 16 17 for (int S=1; S<=m; ++S) 18 { 19 if (cnt[S]==1) {f[S]=1; continue;} 20 for (int i=1; i<=n; ++i) 21 if ((S&(1<<i-1)) && sum[S]-a[i]>0) 22 (f[S]+=f[S^(1<<i-1)])%=MOD; 23 } 24 25 g[0]=1; 26 for (int S=1; S<=m; ++S) 27 { 28 if (sum[S]>0) {g[S]=0; continue;} 29 if (cnt[S]==1) {g[S]=1; continue;} 30 for (int i=1; i<=n; ++i) 31 if (S&(1<<i-1)) 32 (g[S]+=g[S^(1<<i-1)])%=MOD; 33 } 34 int ans=0; 35 for (int S=1; S<=m; ++S) 36 (ans+=1ll*sum[S]*f[S]%MOD*g[m^S]%MOD)%=MOD; 37 ans=(ans%MOD+MOD)%MOD; 38 printf("%d\n",ans); 39 }