LOJ#6433. 「PKUSC2018」最大前缀和 状压dp

原文链接https://www.cnblogs.com/zhouzhendong/p/LOJ6433.html

题解

枚举一个集合 S ,表示最大前缀和中包含的元素集为 S ,然后求出有多少个排列是这样的。

对于左边和右边分别考虑,我们可以发现:

左边:每一个后缀和都 >=0

右边:每一个前缀和都 <0

然后就只需要用两个 dp 分别求出每一个集合的元素的排列中分别满足上述条件的方案数即可。

注意一下题目要求最大前缀和非空。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
int read(){
	int x=0,f=1;
	char ch=getchar();
	while (!isdigit(ch)&&ch!='-')
		ch=getchar();
	if (ch=='-')
		f=0,ch=getchar();
	while (isdigit(ch))
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return f?x:-x;
}
const int N=20,mod=998244353;
int n,a[N];
int Log[1<<N],sum[1<<N],dp1[1<<N],dp2[1<<N];
void Add(int &x,int y){
	if ((x+=y)>=mod)
		x-=mod;
}
int main(){
	n=read();
	for (int i=0;i<n;i++)
		a[i]=read();
	for (int i=2;i<(1<<n);i++)
		Log[i]=Log[i>>1]+1;
	for (int i=1;i<(1<<n);i++)
		sum[i]=sum[i^(i&-i)]+a[Log[i&-i]];
	dp1[0]=1;
	for (int i=0;i<n;i++)
		dp2[1<<i]=1;
	for (int i=1;i<(1<<n);i++)
		for (int j=0;j<n;j++)
			if (i>>j&1){
				if (sum[i]<0)
					Add(dp1[i],dp1[i^(1<<j)]);
				if (sum[i]-a[j]>=0)
					Add(dp2[i],dp2[i^(1<<j)]);
			}
	int ans=0,base=(1<<n)-1;
	for (int i=1;i<(1<<n);i++)
		ans=(1LL*sum[i]*dp2[i]%mod*dp1[i^base]+ans)%mod;
	ans=(ans+mod)%mod;
	printf("%d",ans);
	return 0;
}

  

posted @ 2019-01-24 21:46  zzd233  阅读(200)  评论(0编辑  收藏  举报