【描述】
小 C 是一个算法竞赛爱好者,有一天小 C 遇到了一个非常难的问题:求一个序列的最大子段和。
但是小 C 并不会做这个题,于是小 C 决定把序列随机打乱,然后取序列的最大前缀和作为答案。
小 C 是一个非常有自知之明的人,他知道自己的算法完全不对,所以并不关心正确率,他只关心求出的解的期望值,现在请你帮他解决这个问题,由于答案可能非常复杂,所以你只需要输出答案乘上 n! 后对 998244353 取模的值,显然这是个整数。
注:最大前缀和的定义:
∀
i
∈
[
1
,
n
]
,
∑
j
=
1
i
a
j
∀i∈[1,n],∑^i_{j=1}a_j
∀i∈[1,n],∑j=1iaj的最大值。
数据规模:
n
<
=
20
,
∑
i
=
1
n
∣
a
i
∣
<
1
e
9
n<=20,\sum_{i=1}^n |a_i|<1e9
n<=20,∑i=1n∣ai∣<1e9
【思路】
首先,我们先考虑一个最大前缀和的必要条件:设最大的位置在i,那么
∀
k
∈
[
i
+
1
,
n
]
,
∑
j
=
i
+
1
k
a
j
<
0
总
是
成
立
∀k∈[i+1,n],∑^k_{j=i+1}a_j<0总是成立
∀k∈[i+1,n],∑j=i+1kaj<0总是成立。这给我们提供了一个思路,如果我们保证i处的前缀和是前i个位置里最大的,将序列从i,i+1处断开,后面部分的序列的前缀和总是小于等于0,即可保证i是最大的前缀和。剩下的便是一个计数问题,题目所求即所有排列最大前缀和之和。由于我们把序列分为了两部分,转移时我们需要知道某个数是否在其中一个序列中,所以考虑状压dp。记
f
[
s
]
f[s]
f[s]表示
i
∈
s
,
a
i
i∈s,a_i
i∈s,ai构成的序列且最大前缀和在第
∣
s
∣
|s|
∣s∣位的方案数,即前半部分序列的信息。
g
[
s
]
g[s]
g[s]表示
i
∈
s
,
a
i
i∈s,a_i
i∈s,ai构成的序列且每个位置的前缀和均小于等于0。为方便转移,记
s
u
m
[
s
]
sum[s]
sum[s]为
∑
i
∈
s
a
i
\sum_{i∈s} a_i
∑i∈sai。g的转移很显然,我们考虑从前到后依次填数,保证每次填以后的状态小于等于0即可,即:
g
[
s
∪
i
]
=
∑
s
u
m
s
∪
i
≤
0
g
[
s
]
g[s \cup i]= \sum_{sum_{s \cup i}\le0} g[s]
g[s∪i]=sums∪i≤0∑g[s]
f的转移很头疼,因为我们要确保当前位置的前缀和最大。我们考虑倒着填数:
f
[
s
∪
i
]
=
∑
s
u
m
s
>
0
f
[
s
]
f[s\cup i]=\sum_{sum_s>0}f[s]
f[s∪i]=sums>0∑f[s]
只要每次填之前的sum大于0,不管这一位填什么,后面的sum加上这个数构成的前缀和一定是最大的。注意,由于一个种排列可能有多个位置满足前缀和最大,所以f和g的关于sum的转移条件只能有一个取等,否则会重复计算贡献。答案由我们最开始的思路可得:
a
n
s
=
∑
s
∈
U
s
u
m
[
s
]
∗
f
[
s
]
∗
g
[
ans=\sum_{s∈U}sum[s]*f[s]*g[
ans=∑s∈Usum[s]∗f[s]∗g[U^s
]
]
]。
代码:
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1.1e6+5,mod=998244353;
inline int red(){
int data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}
int f[N]={1},sum[N],g[N]={1},U,ans=0,n;
inline void Add(int&a,const int&b){((a+=b)>=mod)&&(a-=mod);}
inline int dec(const int&a,const int&b){return a<b?a-b+mod:a-b;}
inline int mul(const int&a,const int&b){return 1ll*a*b%mod;}
int main(){n=red();U=1<<n;
for(int re i=0;i^n;++i)sum[1<<i]=red(),f[1<<i]=1;
for(int re i=1;i^U;++i)sum[i]=sum[i^(i&(-i))]+sum[i&(-i)];
for(int re i=1;i^U;++i)if(sum[i]>0)
for(int re j=0;j^n;j++)
if(!(i&(1<<j)))Add(f[i|(1<<j)],f[i]);
for(int re i=1;i^U;++i)if(sum[i]<=0)
for(int re j=0;j^n;++j)
if((i&(1<<j)))Add(g[i],g[i^(1<<j)]);
for(int re i=1;i^U;++i)
Add(ans,mul(dec(sum[i]%mod,0),mul(f[i],g[(U-1)^i])));
cout<<ans<<"\n";
}