【洛谷P5369】最大前缀和

题目

题目链接:https://www.luogu.com.cn/problem/P5369
小 C 是一个算法竞赛爱好者,有一天小 C 遇到了一个非常难的问题:求一个序列的最大子段和。
但是小 C 并不会做这个题,于是小 C 决定把序列随机打乱,然后取序列的最大前缀和作为答案。
小 C 是一个非常有自知之明的人,他知道自己的算法完全不对,所以并不关心正确率,他只关心求出的解的期望值,现在请你帮他解决这个问题,由于答案可能非常复杂,所以你只需要输出答案乘上 (n!) 后对 (998244353) 取模的值,显然这是个整数。
注:最大前缀和的定义:(forall i in [1,n])(sum_{j=1}^{i}a_j) 的最大值。
(nleq 20)

思路

(sum[s]) 表示集合 (s) 中所有元素之和。设 (f[s]) 表示集合为 (s) 时,最大前缀和恰好为 (sum[s]) 的方案数。(g[s]) 表示集合为 (s) 时,最大前缀和为 (0) 的方案数。
那么答案显然就是

[sum^{2^n-1}_{i=1}sum[i] imes f[i] imes g[2^n-1-i] ]

接下来考虑转移。
如果 (sum[s]geq 0),那么我们可以通过在集合 (s) 前加入一个数字 (i) 来转移到 (f[s m xor i])。否则无法对 (f[s m xor i]) 产生贡献。
如果 (sum[s]<0),那么对于一个在 (s) 里的数 (i)(g[s]) 可以通过 (g[s m xor i]) 转移过来。
时间复杂度 (O(2^nn))

代码

#include <bits/stdc++.h>
using namespace std;

const int N=25,M=(1<<20),MOD=998244353;
int n,ans,lim,a[N],f[M],g[M],sum[M];

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]),f[1<<i-1]=1;
	g[0]=1; lim=1<<n;
	for (int s=1;s<lim;s++)
	{
		for (int i=1;i<=n;i++)
			if (s&(1<<i-1)) sum[s]+=a[i];
		if (sum[s]<0)
			for (int i=1;i<=n;i++)
				if (s&(1<<i-1)) g[s]=(g[s]+g[s^(1<<i-1)])%MOD;
		if (sum[s]>=0)
			for (int i=1;i<=n;i++)
				if (!(s&(1<<i-1))) f[s|(1<<i-1)]=(f[s|(1<<i-1)]+f[s])%MOD;
	}
	for (int i=0;i<lim;i++)
		ans=(ans+1LL*sum[i]%MOD*f[i]%MOD*g[(lim-1)^i])%MOD;
	cout<<(ans%MOD+MOD)%MOD;
	return 0;
}
原文地址:https://www.cnblogs.com/stoorz/p/15017090.html