【LOJ6433】【PKUSC2018】最大前缀和
【LOJ6433】【PKUSC2018】最大前缀和
题面
题目描述
小 C 是一个算法竞赛爱好者,有一天小 C 遇到了一个非常难的问题:求一个序列的最大子段和。
但是小 C 并不会做这个题,于是小 C 决定把序列随机打乱,然后取序列的最大前缀和作为答案。
小 C 是一个非常有自知之明的人,他知道自己的算法完全不对,所以并不关心正确率,他只关心求出的解的期望值,现在请你帮他解决这个问题,由于答案可能非常复杂,所以你只需要输出答案乘上 \(n!\) 后对 \(998244353\) 取模的值,显然这是个整数。
注:最大前缀和的定义:\(\forall i \in [1,n]\),\(\sum_{j=1}^{i}a_j\) 的最大值。
输入格式
第一行一个正整数 \(n\),表示序列长度。
第二行 \(n\)个数,表示原序列 \(a[1..n]\),第 \(i\) 个数表示 \(a[i]\) 。
输出格式
输出一个非负整数,表示答案。
样例
输入样例
2
-1 2
输出样例
3
提示与说明
对于\(10\%\)的数据,有\(1\leq n\leq 9\)
对于\(40\%\)的数据,有\(1\leq n\leq 15\)
另有\(10\%\)的数据,满足\(a\)中最多只有一个负数。
另有\(10\%\)的数据,满足\(|a[i]|\leq 2\)
对于\(100\%\)的数据,满足\(1\leq n\leq 20\),\(\sum_{i=1}^{n}|a[i]|\leq 10^9\)
题解
因为假如有\(\forall i\sum_{j=1}^{p-1} a_i\),且\(\exists \sum_{j=p}^na_j \geq 0\)
最优解肯定不在\(1-p-1\)中,所以
预处理几个东西(i是子集):
\(sum[i]:\)状态为\(i\)时所有数的和
\(f[i]:\)状态为\(i\)时和为\(sum[i]\)的方案数
\(g[i]:\)状态为\(i\)时排列为正的方案数
转移看代码
压行真舒服
代码
#include <iostream>
#include <cstdio>
using namespace std;
#define rep(i, from, to) for(int i = (from); i <= (to); i++)
typedef long long ll;
const int Mod = 998244353;
int N, a[20], f[1 << 20], g[1 << 20];
ll sum[1 << 20];
void pls(int &x, int y) { x += y; if (x >= Mod) x -= Mod; }
int main () {
cin >> N; rep(i, 0, N - 1) cin >> a[i];
int S = (1 << N) - 1;
g[0] = 1;
rep (i, 0, N - 1) { f[1 << i] = 1; rep(s, 0, S) if (s & (1 << i)) sum[s] += a[i]; }
rep (s, 0, S) {
if (sum[s] > 0) rep (i, 0, N - 1) { if ((s & (1 << i)) == 0) pls(f[s | (1 << i)], f[s]); }
else rep (i, 0, N - 1) if (s & (1 << i)) pls(g[s], g[s ^ (1 << i)]);
}
rep (s, 0, S) { sum[s] %= Mod; while (sum[s] < 0) sum[s] += Mod; }
int ans = 0; rep (s, 0, S) pls(ans, 1ll * sum[s] * f[s] % Mod * g[S ^ s] % Mod);
printf("%d\n", ans);
return 0;
}