bzoj5369: [Pkusc2018]最大前綴和 狀壓Dp 計數Dp

bzoj5369: [Pkusc2018]最大前綴和

Description

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

分析

考場不會寫系列。
基本思路是狀壓ss,計算sum[s]sum[s]作爲最大前綴和的方案,其中sum[s]sum[s]爲狀態ss各個數的和。
首先爲了避免重複,如有多解,用下標最小的最大前綴和標記每種方案。
考慮一個前綴和S[1p]S[1\cdots p]作爲最大前綴和的等價條件。

  • 序列2p2\cdots p不存在小於等於0的後綴和。
  • 序列p+1np+1\cdots n不存在大於0的前綴和。

必要性:假設存在大於0的前綴和或小於等於0的後綴和,考慮加上/減去那段前綴/後綴和,就一定找到了一個更加優秀的前綴和,矛盾。
充分性:假設一個更優秀的解,如果在pp之後,兩端前綴和作差必然得到一個序列p+1np+1\cdots n的大於0的前綴和,在pp之前同理,矛盾。
這兩個條件的好處是他們將一個最優性問題轉化成了判定性問題。這樣提供了一個比較簡便的轉移思路。
同時發現這兩個條件是獨立的,前一個條件與後一個條件是兩個獨立的問題,於是我們可以把目標集合劃分成兩個部分,前一個部分滿足條件1,將其作爲前綴,後一個部分滿足條件2,將其作爲後綴,乘法原理即可計算方案數。
具體地,設f[s]f[s]表示集合ss爲一個合法的滿足條件1的前綴的方案數。枚舉一個新的數xx,如果這個數可以合法地放在ss最前,必有sum[s]&gt;0sum[s]&gt; 0(滿足條件1),類似地,設g[s]g[s]表示集合ss爲一個合法的滿足條件2的後綴的方案數,枚舉一個新的數xx,如果這個數可以合法地放在ss最後,則必有sum[s]+a[x]0sum[s]+a[x]\le 0
最終的答案即爲f[s]g[alls]\sum f[s]\cdot g[all - s]

代碼

#include<bits/stdc++.h>
const int P = 998244353, S = 1048576;
int ri() {
    char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
    for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int f[S], g[S], sum[S], bn[21], n;
void Up(int &a, int b) {a += b; a %= P;}
int main() {
    g[0] = bn[0] = 1; for(int i = 1;i <= 20; ++i) bn[i] = bn[i - 1] << 1;
    n = ri();
    for(int i = 0;i < n; ++i) {
        int x = ri(); f[bn[i]] = 1;
        for(int s = 0;s < bn[n]; ++s) 
            if(s & bn[i]) sum[s] += x;
    }
    for(int s = 0;s < bn[n]; ++s) {
        if(sum[s] > 0) {
            for(int i = 0;i < n; ++i) 
                if(~s & bn[i]) Up(f[s | bn[i]], f[s]);
        }
        else {
            for(int i = 0;i < n; ++i)
                if(s & bn[i]) Up(g[s], g[s ^ bn[i]]);
        }
    }
    int r = 0;
    for(int s = 0;s < bn[n]; ++s) Up(r, 1LL * sum[s] * f[s] % P * g[bn[n] - 1 ^ s] % P);
    printf("%d\n", (r + P) % P);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章