🍕🏠🌋 当前时间是:

 

PKUSC2018 最大前缀和

题意

给一个长度为 \(n\) 的整数序列,求其 \(n!\) 种排列方式的最大前缀和(不能为空)的总和。

  • \(n\leq 20\)

解法

设全集为 \(U\),考虑枚举作为最大前缀和的子集 \(S\)。那么要求的就是 \(S\) 排列后严格最大前缀和在最后一个元素取到和方案数和 \(U\backslash S\) 排列后每个前缀和都小于等于 0 的方案数。

后者可以枚举最后一个数填什么转移,前者可以枚举上一个取到严格最大前缀和的子集是哪一个转移,需要注意处理只选一个数的边界情况,复杂度是 \(O(3^n+2^nn)\) 的。

一个精妙的 trick 是考虑 \(S\) 在最后一个位置取到严格最大前缀和相当于把它 reverse 之后前缀和大于 0,这样就可以类比每个前缀和都小于等于 0 做了,同样需要处理只有一个数的情况。

code

代码的边界很不好写。

#include<bits/stdc++.h>

using namespace std;
using E=long long;
const E mod=998244353;

int n;
vector<E> a;

int main(){

  cin>>n;
  a.resize(n);
  for(int i=0; i<n; i++){
    cin>>a[i];
  }

  vector<E> f(1<<n),g(1<<n),sum(1<<n);
  f[0]=g[0]=1;

  for(int msk=1; msk<(1<<n); msk++){
    for(int i=0; i<n; i++) if(msk>>i&1) sum[msk]+=a[i];
    if(sum[msk]<=0) for(int i=0; i<n; i++){
      if((msk>>i&1)==0) continue;
      g[msk]=(g[msk]+g[msk^(1<<i)])%mod;
    }
    if((msk&-msk)==msk) f[msk]=1;
    else for(int i=0; i<n; i++){
      if(msk>>i&1) if(sum[msk^(1<<i)]>0) f[msk]=(f[msk]+f[msk^(1<<i)])%mod;
    }
  }

  E ans=0;
  for(int msk=1; msk<(1<<n); msk++){
    sum[msk]%=mod;
    ans=(ans+f[msk]*g[((1<<n)-1)^msk]%mod*sum[msk])%mod;
    //cerr<<msk<<' '<<f[msk]<<' '<<ans<<endl;
  }

  cout<<(ans%mod+mod)%mod;

  return 0;
}
posted @ 2024-11-15 15:57  zzafanti  阅读(30)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end