【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\%)的数据,有(1leq nleq 9)
对于(40\%)的数据,有(1leq nleq 15)
另有(10\%)的数据,满足(a)中最多只有一个负数。
另有(10\%)的数据,满足(|a[i]|leq 2)
对于(100\%)的数据,满足(1leq nleq 20),(sum_{i=1}^{n}|a[i]|leq 10^9)
题解
因为假如有(forall isum_{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
", ans);
return 0;
}