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;
}

转载于:https://www.cnblogs.com/Yuzao/p/9304011.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值