「PKUSC2018」最大前缀和(状压dp)

状压好题啊。

一眼看出时间复杂度 \(O(n2^n)\),然后开始想正解。

然后设 \(dp_i\) 为前缀状态为 \(i\) 时的方案数,所以这时候 \(sum_i\) 一定是单峰的。

可以推导出:

\[(1\leq j<i)\ sum_i\geq sum_j\Rightarrow sum_i-sum_j\geq 0 \]

\[(i<j\leq n)\ sum_j<sum_i\Rightarrow sum_j-sum_i<0 \]

那么我们相当于求两个序列拼凑起来,一个序列前缀和除第一项始终 \(\geq 0\)(因为 \(sum_i-sum_1\) 不包括第一项,但是 \(sum_n-sum_i\) 包括最后一项),一个前缀和始终 \(<0\)\(f_i\) 表示前缀和为负数的,\(g_i\) 表示前缀和为正数的,那么 \(g_i\) 拼凑时 \(i>0\)。每一个状态对于答案的贡献为 \(g_i\times f_{lim\ xor\ i}\times (sum_i+p)\%p\)

时间复杂度 \(O(n2^n)\)

\(Code\ Below:\)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int p=998244353;
int n,lim,a[20],sum[1<<20],f[1<<20],g[1<<20];

int main()
{
	scanf("%d",&n);lim=(1<<n)-1;
	for(int i=0;i<n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=lim;i++)
		for(int j=0;j<n;j++)
			if(i&(1<<j)){
				sum[i]=sum[i^(1<<j)]+a[j];
				break;
			}
	f[0]=g[0]=1;
	for(int i=1;i<=lim;i++)
		if(sum[i]<0)
			for(int j=0;j<n;j++)
				if((i&(1<<j))&&(sum[i^(1<<j)]<0||!(i^(1<<j)))) f[i]=(f[i]+f[i^(1<<j)])%p;
	for(int i=1;i<=lim;i++)
		for(int j=0;j<n;j++)
			if((i&(1<<j))&&(sum[i^(1<<j)]>=0||!(i^(1<<j)))) g[i]=(g[i]+g[i^(1<<j)])%p;
	int ans=0;
	for(int i=1;i<=lim;i++) ans=(ans+(ll)g[i]*f[lim^i]%p*(sum[i]+p)%p)%p;
	printf("%d\n",ans);
	return 0;
}
posted @ 2019-01-04 07:25  Owen_codeisking  阅读(226)  评论(0编辑  收藏  举报