[PKUSC2018]最大前缀和——状压DP

题目链接:

[PKUSC2018]最大前缀和

设$f[S]$表示二进制状态为$S$的序列,任意前缀和都小于等于$0$的方案数。

设$g[S]$表示二进制状态为$S$的序列是整个序列的最大前缀和的方案数。

设$sum[S]$表示二进制状态为$S$的序列的每个数的和。

那么答案就是$\sum\limits_{S=1}^{2^n-1}sum[S]*g[S]*f[(2^n-1)-S]$。

对于$f[S]$,转移相当于在序列前面加一个数,只有当前集合中数的和小于等于$0$时可以转移。

对于$g[S]$,只能从和大于$0$的子集转移过来。

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<cstdio>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int mod=998244353;
int sum[3000000];
int f[3000000];
int g[3000000];
int v[3000000];
int n;
int ans;
int mask;
void add(int &x,int y)
{
	x+=y;
	if(x>mod)x-=mod;
}
int main()
{
	scanf("%d",&n);
	mask=(1<<n)-1;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&v[1<<(i-1)]);
	}
	for(int i=1;i<=mask;i++)
	{
		sum[i]=sum[i-(i&-i)]+v[i&-i];
	}
	f[0]=1;
	for(int i=1;i<=n;i++)
	{
		g[1<<(i-1)]=1;
	}
	for(int i=1;i<=mask;i++)
	{
		if(sum[i]>0)
		{
			for(int j=i^mask;j;j-=j&-j)
			{
				int k=j&-j;
				add(g[i|k],g[i]);
			}
		}
		else
		{
			for(int j=i;j;j-=j&-j)
			{
				int k=j&-j;
				add(f[i],f[i^k]);
			}
		}
	}
	for(int i=1;i<=mask;i++)
	{
		ans=(ans+1ll*g[i]*f[mask^i]%mod*sum[i]%mod)%mod;
	}
	printf("%d",(ans%mod+mod)%mod);
}
posted @ 2019-05-29 10:41  The_Virtuoso  阅读(240)  评论(0编辑  收藏  举报