题意
给一个长度为 \(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;
}
标签:前缀,int,sum,枚举,msk,PKUSC2018,最大
From: https://www.cnblogs.com/FreshOrange/p/18548118