【PKUSC2018】最大前缀和

  • 上午的国庆大阅兵有意思

Description

  https://loj.ac/problem/6433

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;
}
posted @ 2019-10-01 18:06  大本营  阅读(244)  评论(0编辑  收藏  举报