1. 程式人生 > >[PKUSC2018]最大字首和 狀壓DP

[PKUSC2018]最大字首和 狀壓DP

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]sum[i]f[i]*g[j]*sum[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;
}