bzoj 5369: [Pkusc2018]最大前缀和

Description

小C是一个算法竞赛爱好者,有一天小C遇到了一个非常难的问题:求一个序列的最大子段和。
但是小C并不会做这个题,于是小C决定把序列随机打乱,然后取序列的最大前缀和作为答案。
小C是一个非常有自知之明的人,他知道自己的算法完全不对,所以并不关心正确率,他只关心求出的解的期望值,
现在请你帮他解决这个问题,由于答案可能非常复杂,所以你只需要输出答案乘上n!后对998244353取模的值,显然这是个整数。
注:最大前缀和的定义:i∈[1,n],Sigma(aj)的最大值,其中1<=j<=i

Solution

注意到前缀和的取值只有 \(2^n\) 种.
然后可以枚举每一个集合的元素当最大前缀和 , 那么这个集合的元素排列之后每一个后缀都必须大于 \(0\) , 且这个集合的补集排列之后必须保证每一个前缀和都小于 \(0\).
那么状压 \(DP\) 就行了 , 设 \(f[i]\) 表示集合 \(i\) 作为最大前缀和且排列之后每个后缀都大于 \(0\) 的方案数 , \(g[i]\) 表示集合 \(i\) 中元素排列之后每个前缀都小于 \(0\) 的方案数.
强制 \(f,g\) 必须在合法的时候才能转移就行了.

#include<bits/stdc++.h>
using namespace std;
const int N=25,mod=998244353;
int f[1<<20],n,a[N],m,g[1<<20],sum[1<<20];
int main(){
  freopen("pp.in","r",stdin);
  freopen("pp.out","w",stdout);
  cin>>n,m=(1<<n)-1;
  for(int i=0;i<n;i++)cin>>a[i];
  for(int i=0;i<n;i++)
	  for(int j=0;j<=m;j++)if(j>>i&1)sum[j]+=a[i];
  for(int i=0;i<n;i++)f[1<<i]=1,g[1<<i]=1;
  for(int i=0;i<=m;i++){
	  if(sum[i]>0){
		  for(int j=0;j<n;j++)
			  if(~i>>j&1)f[i^(1<<j)]=(f[i^(1<<j)]+f[i])%mod;
	  }
	  else{
		  for(int j=0;j<n;j++)
			  if(~i>>j&1)g[i^(1<<j)]=(g[i^(1<<j)]+g[i])%mod;
	  }
  }
  g[0]=1;
  int ans=0;
  for(int i=0;i<=m;i++)
	  if(sum[m^i]<=0)ans=(ans+1ll*f[i]*sum[i]%mod*g[m^i])%mod;
  cout<<(ans+mod)%mod;
  return 0;
}

posted @ 2018-07-13 11:27  PIPIBoss  阅读(387)  评论(0编辑  收藏  举报