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
分析
考场不会写系列。
基本思路是状压
s
s
s,计算
s
u
m
[
s
]
sum[s]
sum[s]作为最大前缀和的方案,其中
s
u
m
[
s
]
sum[s]
sum[s]为状态
s
s
s各个数的和。
首先为了避免重复,如有多解,用下标最小的最大前缀和标记每种方案。
考虑一个前缀和
S
[
1
⋯
p
]
S[1\cdots p]
S[1⋯p]作为最大前缀和的等价条件。
- 序列 2 ⋯ p 2\cdots p 2⋯p不存在小于等于0的后缀和。
- 序列 p + 1 ⋯ n p+1\cdots n p+1⋯n不存在大于0的前缀和。
必要性:假设存在大于0的前缀和或小于等于0的后缀和,考虑加上/减去那段前缀/后缀和,就一定找到了一个更加优秀的前缀和,矛盾。
充分性:假设一个更优秀的解,如果在
p
p
p之后,两端前缀和作差必然得到一个序列
p
+
1
⋯
n
p+1\cdots n
p+1⋯n的大于0的前缀和,在
p
p
p之前同理,矛盾。
这两个条件的好处是他们将一个最优性问题转化成了判定性问题。这样提供了一个比较简便的转移思路。
同时发现这两个条件是独立的,前一个条件与后一个条件是两个独立的问题,于是我们可以把目标集合划分成两个部分,前一个部分满足条件1,将其作为前缀,后一个部分满足条件2,将其作为后缀,乘法原理即可计算方案数。
具体地,设
f
[
s
]
f[s]
f[s]表示集合
s
s
s为一个合法的满足条件1的前缀的方案数。枚举一个新的数
x
x
x,如果这个数可以合法地放在
s
s
s最前,必有
s
u
m
[
s
]
>
0
sum[s]> 0
sum[s]>0(满足条件1),类似地,设
g
[
s
]
g[s]
g[s]表示集合
s
s
s为一个合法的满足条件2的后缀的方案数,枚举一个新的数
x
x
x,如果这个数可以合法地放在
s
s
s最后,则必有
s
u
m
[
s
]
+
a
[
x
]
≤
0
sum[s]+a[x]\le 0
sum[s]+a[x]≤0
最终的答案即为
∑
f
[
s
]
⋅
g
[
a
l
l
−
s
]
\sum f[s]\cdot g[all - s]
∑f[s]⋅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;
}