PKUSC2018 最大前缀和
最大前缀和
小 C 是一个算法竞赛爱好者,有一天小 C 遇到了一个非常难的问题:求一个序列的最大子段和。
但是小 C 并不会做这个题,于是小 C 决定把序列随机打乱,然后取序列的最大前缀和作为答案。
小 C 是一个非常有自知之明的人,他知道自己的算法完全不对,所以并不关心正确率,他只关心求出的解的期望值,现在请你帮他解决这个问题,由于答案可能非常复杂,所以你只需要输出答案乘上 \(n!\) 后对 \(998244353\) 取模的值,显然这是个整数。
注:最大前缀和的定义:\(\forall i \in [1,n]\),\(\sum_{j=1}^{i}a_j\)的最大值。
对于\(100\%\)的数据,满足\(1\leq n\leq 20\),\(\sum_{i=1}^{n}|a[i]|\leq 10^9\)。
题解
https://www.cnblogs.com/zjp-shadow/p/9141971.html
不难发现,成为最大前缀和位置 \(p\) 后面的所有前缀都不能 \(>0\)。如果 \(>0\) 那么后面必存在一点可以替换当前的答案。
有了这个思路,那我们可以把每个序列拆成两段考虑,而分割点就是位置 \(p\)。记 \(sum_s\) 为 \(s\) 这个状态所有点的代数和。
-
记 \(f(s)\) 表示 \(s\) 集合的排列的最大前缀和等于 \(sum_s\) 且其他前缀和严格更小的方案数。这里规定其他前缀和更小是为了避免算重。
那么若 \(sum_s>0\),我们便可以把它放到某个单点的后面。
\[f(\{u\})\times f(s) \rightarrow f(\{u\}\cup s) \] -
记 \(g(s)\) 表示 \(s\) 集合的排列的所有前缀和都 \(\leq 0\) 的方案数。
若 \(sum_{s\cup \{u\}}\leq 0\),那么我们便可以把 \(s\) 放到 \(u\) 的前面。
\[g(s)\times g(\{u\}) \rightarrow g(s\cup \{u\}) \] -
答案为
\[ans=\sum_s sum_s\times f(s)\times g([n]\setminus s) \]
时间复杂度 \(O(2^n n)\)。
CO int N = 20;
int n, sum[1 << N];
int f[1 << N], g[1 << N];
int main() {
int n = read<int>();
for (int i = 0; i < n; ++i) read(sum[1 << i]);
for (int s = 0; s < 1 << n; ++s) sum[s] = sum[s ^ lowbit(s)] + sum[lowbit(s)];
for (int i = 0; i < n; ++i) f[1 << i] = 1;
for (int s = 0; s < 1 << n; ++s)
if (sum[s] > 0)
for (int i = 0; i < n; ++i)
if (~s >> i & 1)
f[s | 1 << i] = add(f[s | 1 << i], f[s]);
g[0] = 1;
for (int s = 0; s < 1 << n; ++s)
if (sum[s] <= 0)
for (int i = 0; i < n; ++i)
if (s >> i & 1)
g[s] = add(g[s], g[s ^ 1 << i]);
int ans = 0;
for (int s = 0; s < 1 << n; ++s) ans = add(ans, mul(sum[s] % mod + mod, mul(f[s], g[(1 << n) - 1 - s])));
printf("%d\n", ans);
return 0;
}
以前的分析
乘上\(n!\),所谓期望其实就是每种最大前缀和乘上方案数的乘积的和。
参照Boss.Pi的题解。
看数据范围,考虑状压dp。注意到前缀和的取值只有 \(2^n\) 种.
然后可以枚举每一个集合的元素当最大前缀和 , 那么这个集合的元素排列之后每一个后缀都必须大于 \(0\) , 且这个集合的补集排列之后必须保证每一个前缀和都小于 \(0\).
那么状压 DP 就行了 , 设 \(f[i]\) 表示集合 \(i\) 作为最大前缀和且排列之后每个后缀都大于 \(0\) 的方案数 , \(g[i]\) 表示集合 \(i\) 中元素排列之后每个前缀都小于 \(0\) 的方案数.
强制 \(f,g\) 必须在合法的时候才能转移就行了.
时间复杂度\(O(n 2^n)\)
关于转移的问题
就算f加上j转移到了不合法的状态,以后也不会用到。
所以是对的。
co int N=22,mod=998244353;
int n;
int a[N];
int sum[1<<N],f[1<<N],g[1<<N];
il int add(int x,int y)
{
return (x+y)%mod;
}
il int mul(int x,int y)
{
return (ll)x*y%mod;
}
int main()
{
read(n);
for(int i=0;i<n;++i)
read(sum[1<<i]);
#define lowbit(x) (x&-x)
for(int i=0;i<(1<<n);++i)
sum[i]=sum[i-lowbit(i)]+sum[lowbit(i)];
for(int i=0;i<n;++i)
f[1<<i]=1,g[1<<i]=1;
for(int i=0;i<(1<<n);++i)
{
if(sum[i]>0)
{ // edit 1: the big brace is important
for(int j=0;j<n;++j)
if(~i>>j&1)
f[i|(1<<j)]=add(f[i|(1<<j)],f[i]);
}
else
{
for(int j=0;j<n;++j)
if(~i>>j&1)
g[i|(1<<j)]=add(g[i|(1<<j)],g[i]);
}
}
g[0]=1;
int ans=0;
for(int i=0;i<(1<<n);++i)
if(sum[((1<<n)-1)^i]<=0)
ans=add(ans,mul(f[i],mul(sum[i],g[((1<<n)-1)^i])));
printf("%d",(ans+mod)%mod);
return 0;
}
Hint
大括号的问题,害得我交了23次。