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
分析
考場不會寫系列。
基本思路是狀壓,計算作爲最大前綴和的方案,其中爲狀態各個數的和。
首先爲了避免重複,如有多解,用下標最小的最大前綴和標記每種方案。
考慮一個前綴和作爲最大前綴和的等價條件。
- 序列不存在小於等於0的後綴和。
- 序列不存在大於0的前綴和。
必要性:假設存在大於0的前綴和或小於等於0的後綴和,考慮加上/減去那段前綴/後綴和,就一定找到了一個更加優秀的前綴和,矛盾。
充分性:假設一個更優秀的解,如果在之後,兩端前綴和作差必然得到一個序列的大於0的前綴和,在之前同理,矛盾。
這兩個條件的好處是他們將一個最優性問題轉化成了判定性問題。這樣提供了一個比較簡便的轉移思路。
同時發現這兩個條件是獨立的,前一個條件與後一個條件是兩個獨立的問題,於是我們可以把目標集合劃分成兩個部分,前一個部分滿足條件1,將其作爲前綴,後一個部分滿足條件2,將其作爲後綴,乘法原理即可計算方案數。
具體地,設表示集合爲一個合法的滿足條件1的前綴的方案數。枚舉一個新的數,如果這個數可以合法地放在最前,必有(滿足條件1),類似地,設表示集合爲一個合法的滿足條件2的後綴的方案數,枚舉一個新的數,如果這個數可以合法地放在最後,則必有
最終的答案即爲
代碼
#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;
}