P5369 [PKUSC2018]最大前缀和

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\)
所以转移为

\[h_{S,0} += h_{S \bigoplus u,1}(sum[S] < 0) \]

\[h_{S,1} += h_{S \bigoplus u,1}(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);
}

posted @ 2022-11-25 19:32  RiverSheep  阅读(81)  评论(0)    收藏  举报