P5369 [PKUSC2018]最大前缀和
题目要我们求每一种排列的最大前缀和,不妨考虑先确定最大前缀和,再计算它的方案数,设\(U\)为全集,那么答案就为\(\sum_{S \subseteq U}sum[S] * f[S]\),其中\(sum[S] = \sum_{i \in S}a_i\),那么我只需要考虑怎么去求\(f[S]\)。
设最最大前缀和为位置为\(1\)到\(x\)的和,那么对于任意\(1<i\le x\),\(\sum_{j = i}^xa_j \ge 0\),对于任意\(x<i\le n\),\(\sum_{j = x + 1}^ia_i<0\)。
这两个限制是独立的,可以分开\(DP\),设\(h_{S,0/1}\)表示前缀点集是\(S\),\(0\)表示\(sum[S] < 0\),\(1\)表示\(sum[S]\ge 0\)的方案数。那么考虑从后向前加数,注意我们不需要保证\(sum[S] \ge 0\)。
所以转移为
设\(g_{S}\)表示后缀点集为\(S\)的方案数,那么转移为
\[g_{S} += g_{S \bigoplus u}(sum[S] < 0) \]所以\(f[S] = (h_{S,0}+h_{S,1})*g_{S}\)
Code
#include<cstdio>
#include<iostream>
#define IN inline
#define LL long long
using namespace std;
const int N = 1 << 21, P = 998244353;
int n; LL a[25], sum[N], f[N][2], g[N];
IN int read() {
int t = 0,res = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) t |= (ch == '-');
for (; isdigit(ch); ch = getchar()) res = (res << 3) + (res << 1) + (ch ^ 48);
return t ? -res : res;
}
int main() {
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i < (1 << n); i++)
for (int j = 1; j <= n; j++)
if ((i >> j - 1) & 1) sum[i] += a[j];
f[0][1] = g[0] = 1;
for (int i = 1; i < (1 << n); i++)
for (int j = 1; j <= n; j++)
if ((i >> j - 1) & 1)
if (sum[i] < 0) (f[i][0] += f[i ^ (1 << j - 1)][1]) %= P, (g[i] += g[i ^ (1 << j - 1)]) %= P;
else (f[i][1] += f[i ^ (1 << j - 1)][1]) %= P;
LL ans = 0;
for (int i = 1; i < (1 << n); i++)
(ans += (sum[i] + P) * (f[i][0] + f[i][1]) % P * g[((1 << n) - 1) ^ i] % P) %= P;
printf("%lld\n", ans);
}