【PKUSC2018】最大前缀和

Description

小 C 是一个算法竞赛爱好者,有一天小 C 遇到了一个非常难的问题:求一个序列的最大子段和。

但是小 C 并不会做这个题,于是小 C 决定把序列随机打乱,然后取序列的最大前缀和作为答案。

小 C 是一个非常有自知之明的人,他知道自己的算法完全不对,所以并不关心正确率,他只关心求出的解的期望值,现在请你帮他解决这个问题,由于答案可能非常复杂,所以你只需要输出答案乘上 \(n!\) 后对 \(998244353\) 取模的值,显然这是个整数。

注:最大前缀和的定义:\(\forall i \in [1,n],\sum_{j=1}^{i}a_j\)的最大值。

Input

第一行一个正整数 \(n\),表示序列长度。

第二行 \(n\) 个数,表示原序列 \(a[1..n]\),第 \(i\) 个数表示 \(a[i]\)

Output

输出一个非负整数,表示答案。

Solution

据说是去年的签到题啊。

因为是最大前缀和,设\(\sum_{i=1}^p a_i\)\(a[1..n]\)的最大前缀和,则该序列满足以下性质

  1. \(\forall i\in [2,p] ,\sum_{j=i}^p a_j >0\),不然的话你完全可以找到一个比\(p\)更靠前的位置来得到更大的前缀和。

  2. \(\forall i\in [p+1,n] ,\sum_{j=p+1}^i a_j <=0\),同理,如果不满足,你可以找到一个比\(p\)更靠后的位置来得到更大的前缀和。

考虑\(dp\),其中\(sum_i=\sum_{j \in i} a[j]\)

\(F_i\)表示取\(i\)这个集合的数,满足最大前缀和等于\(sum_i\),因为性质1,所以我们考虑从\(p\)开始倒着取数,有以下转移方程

\[F_{i \bigcup \{j\}} = \sum_{j \notin i } F_{i}, sum_i >0 \]

\(G_i\)表示取\(i\)这个集合的数,满足性质2,

\[G_{i \bigcup \{j\}} = \sum_{j \notin i } G_{i} ,sum_{i \bigcup \{j\}} \leq 0 \]

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int Mod=998244353;
int n,a[50],G[2000010],F[2000010],sum[2000010];
int main()
{
	scanf("%d",&n);
	for (int i=0;i<n;i++) scanf("%d",&a[i]);
	for (int i=0;i<(1<<n);i++)
		for (int j=0;j<n;j++)
			if ((i>>j) & 1) sum[i]=(sum[i]+a[j])%Mod;
	for (int i=0;i<n;i++) 
		F[1<<i]=1;
	for (int i=0;i<(1<<n);i++)
		for (int j=0;j<n;j++)
		{
			if ((i>>j) & 1) continue;
			if (sum[i]>0) F[i|(1<<j)]=(F[i|(1<<j)]+F[i])%Mod;
		}
	G[0]=1;
	for (int i=0;i<(1<<n);i++)
		for (int j=0;j<n;j++)
		{
			if ((i>>j) & 1) continue;
			if (sum[i|(1<<j)]<=0) G[i|(1<<j)]=(G[i|(1<<j)]+G[i])%Mod;
		}
	int ans=0;
	for (int i=0;i<(1<<n);i++)
		ans=(ans+1LL*sum[i]*F[i]%Mod*((1<<n)-i-1?G[(1<<n)-i-1]:1)%Mod+Mod)%Mod;
	printf("%d\n",ans);
	return 0;
}
posted @ 2019-01-19 20:43  Starryskies  阅读(552)  评论(0编辑  收藏  举报