【题解】LuoGu5369:[PKUSC2018]最大前缀和

原题传送门
这种题目真有意思,总是披着期望的外套,然后还不忘说一声答案乘一个xxx,可以证明是整数
这不就是方案和吗,这样子又不是会有人看不出来

求全排列的最大前缀和之和
首先可以发现一个重要性质,对于一个排列,若最大前缀和的结尾位置在 p p p,可以发现
∑ i = x p a i > 0 ( x < = p ) \sum_{i=x}^{p}a_i>0(x<=p) i=xpai>0(x<=p)
∑ i = p + 1 x a i < = 0 ( x > p ) \sum_{i=p+1}^{x}a_i<=0(x>p) i=p+1xai<=0(x>p)
根据这个性质可以状压dp

s u m i sum_i sumi表示状态 i i i的所有数之和
f i f_i fi表示状态 i i i的排列最大前缀和为 s u m i sum_i sumi的方案数
g i g_i gi表示状态 i i i的排列所有前缀和都<=0的方案数
可以得到答案
a n s = ∑ i = 0 2 n − 1 s u m i ∗ f i ∗ g 2 n − 1 − i ans=\sum_{i=0}^{2^n-1}sum_i*f_i*g_{2^n-1-i} ans=i=02n1sumifig2n1i
通过dp得到 f , g f,g f,g
g i + = g i − 2 j ( s u m i < = 0 且 2 j ∈ i ) g_i+=g_{i-2_j}(sum_i<=0\text{且}2^j∈i) gi+=gi2j(sumi<=02ji)
f i + 2 j + = f i ( s u m i > 0 且 2 j ∉ i ) f_{i+2^j}+=f_i(sum_i>0\text{且}2_j∉i) fi+2j+=fi(sumi>02j/i)

Code:

#include 
#define maxn 1200010
#define LL long long
using namespace std;
const LL qy = 998244353;
int n, N, power[25], num[maxn];
LL g[maxn], f[maxn], sum[maxn], ans;

inline int read(){
	int s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

int lowbit(int x){ return x & -x; }
void upd(LL &x, LL y){ if ((x += y) >= qy) x -= qy; }

int main(){
	n = read(), N = (1 << n) - 1;
	power[0] = 1;
	for (int i = 1; i <= 20; ++i) power[i] = power[i - 1] << 1;
	for (int i = 1; i <= n; ++i) num[power[i - 1]] = read();
	for (int i = 1; i <= N; ++i) upd(sum[i], sum[i ^ lowbit(i)] + num[lowbit(i)]);
	g[0] = 1;
	for (int i = 0; i <= N; ++i)
	if (sum[i] <= 0)
		for (int j = 0; j < n; ++j)
			if (i & power[j]) upd(g[i], g[i ^ power[j]]);
	for (int i = 0; i < n; ++i) f[power[i]] = 1;
	for (int i = 0; i <= N; ++i){
		upd(ans, (sum[i] + qy) % qy * f[i] % qy * g[N ^ i] % qy);
		if (sum[i] > 0)
			for (int j = 0; j < n; ++j)
				if (!(i & power[j])) upd(f[i ^ power[j]], f[i]);
	}
	printf("%lld\n", ans);
	return 0;
}

你可能感兴趣的:(题解,LuoGu,DP)