【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]\)的最大前缀和,则该序列满足以下性质
-
\(\forall i\in [2,p] ,\sum_{j=i}^p a_j >0\),不然的话你完全可以找到一个比\(p\)更靠前的位置来得到更大的前缀和。
-
\(\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;
}