[PKUSC2018]最大前缀和 状压DP

版权声明:xgc原创文章,未经允许不得转载。 https://blog.csdn.net/xgc_woker/article/details/82789632

Description
给你一个长度不超过20的序列,对于每一种全排列他都有一个最大前缀和,让你求所有最大前缀和的和。


Sample Input
2
-1 2


Sample Output
3


这题好强啊,感觉好思维。。。
考虑对于一个全排列若他的最大前缀和的位置是pos,那么pos所构成的前缀必定都小于等于0。
然后思考一个状压DP,设sum[i]为i这个状态所有数的总和,
f[i]为这个状态为最大前缀和的方案数,
g[i]为这个状态所构成的前缀全都小于等于0的方案数。
那你对于一个状态i,若j为他的反状态。
答案就为: f [ i ] g [ j ] s u m [ i ] f[i]*g[j]*sum[i]
然后对于f[i]的转移,当前状态i若他大于0,那么你就可以在开头插一个数这样的话是肯定可以保证最大前缀和是存在在新生成的这一段区间的。
对于g[i]的转移,类似,可以思考一下。


#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;
const LL mod = 998244353;

int a[1050000];
LL f[1050000], sum[1050000], g[1050000];

int lowbit(int x) {return x & -x;}

int main() {
	int n; scanf("%d", &n);
	for(int i = 0; i < n; i++) {
		int x; scanf("%d", &x);
		a[1 << i] = x;
	}
	sum[0] = 0;
	for(int i = 1; i < (1 << n); i++) sum[i] = sum[i ^ lowbit(i)] + a[lowbit(i)];
	f[0] = g[0] = 1;
	for(int i = 0; i < (1 << n); i++) {
		if(sum[i] <= 0) {
			for(int j = 0; j < n; j++) if((i >> j & 1)){
				(g[i] += g[i ^ (1 << j)]) %= mod;
			}
		}
	} LL ans = 0;
	for(int i = 0; i < n; i++) f[1 << i] = 1;
	for(int i = 0; i < (1 << n); i++) {
		if(sum[i] > 0) {
			for(int j = 0; j < n; j++) if(!(i >> j & 1)){
				(f[i ^ (1 << j)] += f[i]) %= mod;
			}
		} (ans += f[i] * g[((1 << n) - 1) ^ i] % mod * (sum[i] % mod + mod) % mod) %= mod;
	} printf("%lld\n", (ans % mod + mod) % mod);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/xgc_woker/article/details/82789632
今日推荐