【PKUSC2018】最大前缀和
- 上午的国庆大阅兵有意思
Description
Solution
看数据范围认解法
首先在每种情况出现概率相同的情况下, \(期望 \times 方案数 = 权值和\),即题意就是让你求所有排列的最大前缀和的总和……
我们可以枚举哪些数是最大前缀,显然这些数内部任意交换顺序、其它数内部任意交换顺序 都不会改变这个最大前缀。
一些数要排到前面去成为最大前缀,条件是该前缀除整段外的所有后缀和 \(\gt 0\)(因为最大前缀长度不能是 \(0\)),后面的所有前缀和 \(\le 0\)。
(一个 \(\gt 0\),一个 \(\le 0\) 是因为对于一种排列,若有多个前缀和均为最大,我们只根据最短的前缀统计一次该排序。也可以根据最长的前缀,即一个 \(\ge 0\),一个 \(\lt 0\))
设 \(f(i)\) 表示集合 \(i\) 的数有多少种排列满足所有后缀和 \(\gt 0\),\(g(i)\) 表示集合 \(i\) 的数有多少种排列满足所有前缀和 \(\le 0\)。
\(f\) 的转移是每次往前加一个数,\(g\) 的转移是每次往后加一个数。加一个数只需要判断一下新后/前缀和是否满足条件。
最后把 \(f\) 和 \(g\) 卷起来就好了。
#include<bits/stdc++.h>
#define ll long long
#define N 1048580
#define mod 998244353
using namespace std;
inline int read(){
int x=0; bool f=1; char c=getchar();
for(;!isdigit(c); c=getchar()) if(c=='-') f=0;
for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
if(f) return x; return 0-x;
}
int n,nn,f[N],g[N],ans; ll sum[N];
inline int lowbit(int x) {return x&-x;}
inline void upd(int &x, int y) {x = (x+y) % mod;}
int main(){
n=read(), nn=(1<<n)-1;
for(int i=0; i<n; ++i) sum[1<<i]=read();
for(int i=1; i<=nn; ++i){
int x=lowbit(i);
if(i^x) sum[i]=sum[i^x]+sum[x];
}
for(int i=0; i<n; ++i) f[1<<i]=1;
for(int i=1; i<=nn; ++i) if(sum[i]>0)
for(int j=0; j<n; ++j) if((i&(1<<j))==0)
upd(f[i^(1<<j)], f[i]);
//for(int i=0; i<=nn; ++i) cout<<f[i]<<' '; cout<<endl;
g[0]=1;
for(int i=0; i<=nn; ++i)
for(int j=0; j<n; ++j) if((i&(1<<j))==0 && sum[i^(1<<j)]<=0)
upd(g[i^(1<<j)], g[i]);
//for(int i=0; i<=nn; ++i) cout<<g[i]<<' '; cout<<endl;
for(int i=1; i<=nn; ++i)
upd(ans, (ll)f[i]*g[nn^i]%mod*((sum[i]%mod+mod)%mod)%mod);
cout<<ans<<endl;
return 0;
}
嗯。。。