[PKUSC2018]最大前缀和

[PKUSC2018]最大前缀和
应该是套路题,但是思维还是比较巧妙。
考虑状压。
subtask3 可以设 sum[s] 表示 s 集合的数之和,分负数前后两个部分讨论即可。
subtask4 可以设 dp[n][S] 表示选择前 n 个数后最大前缀和为 S 的方案数,再用一个 sum1[n] 记录前缀和即可转移。
子任务提示我们直接计算最大前缀和的和不好求,所以转为计算方案数。
经过分析可以发现答案一定为一个最大前缀和为自己的序列和一个所有前缀均为负的序列拼起来。
那么设前一个序列为 dp,后一个序列为 g。
则有

\[g[s|{1<<(i-1)}]+=g[s](sum[s|{1<<(i-1)}]<0) \]

也就是将 i 放在当前序列的最后一个,总和仍为负。
但是 dp 并不是很好计数,因为若 a[i]<0 将 i 放在最后后仍有可能合法。
所以我们尝试将 i 放在最前面,这和新放的数在最后等价。
则有

\[dp[s|{1<<(i-1)}]+=dp[s](sum[s]>=0) \]

神奇吧,我也觉得很神奇。
code:

#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=25;const int MAXM=14348910;
const int Mod=998244353;
#define ll long long
int n,m;ll a[MAXN],sum[MAXM],dp[MAXM],g[MAXM],res;
int main(){
	scanf("%d",&n);m=1<<n;for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum[1<<(i-1)]=a[i];
	for(int i=1;i<m;i++) sum[i]=(sum[i^(i&-i)]+sum[i&-i])%Mod;dp[0]=1;g[0]=1;
	for(int s=0;s<m;s++){
		for(int j=1;j<=n;j++){
			if(s&(1<<(j-1))) continue;
			if(sum[s^(1<<(j-1))]<0) g[s|(1<<(j-1))]+=g[s],g[s|(1<<(j-1))]%=Mod;
			if(sum[s]>=0) dp[s|(1<<(j-1))]+=dp[s],dp[s|(1<<(j-1))]%=Mod;
		}
	}
	for(int i=0;i<m;i++) res=(res+((sum[i]*dp[i]%Mod)*g[(m-1)^i]%Mod))%Mod;
	printf("%lld",(res+Mod)%Mod);
	return 0;
}/*
4
-100 33 4 200
*/
posted @ 2023-02-19 21:53  StranGePants  阅读(44)  评论(0编辑  收藏  举报