[PKUSC2018]最大前缀和——状压DP
题目链接:
设$f[S]$表示二进制状态为$S$的序列,任意前缀和都小于等于$0$的方案数。
设$g[S]$表示二进制状态为$S$的序列是整个序列的最大前缀和的方案数。
设$sum[S]$表示二进制状态为$S$的序列的每个数的和。
那么答案就是$\sum\limits_{S=1}^{2^n-1}sum[S]*g[S]*f[(2^n-1)-S]$。
对于$f[S]$,转移相当于在序列前面加一个数,只有当前集合中数的和小于等于$0$时可以转移。
对于$g[S]$,只能从和大于$0$的子集转移过来。
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<vector> #include<cstdio> #include<bitset> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; const int mod=998244353; int sum[3000000]; int f[3000000]; int g[3000000]; int v[3000000]; int n; int ans; int mask; void add(int &x,int y) { x+=y; if(x>mod)x-=mod; } int main() { scanf("%d",&n); mask=(1<<n)-1; for(int i=1;i<=n;i++) { scanf("%d",&v[1<<(i-1)]); } for(int i=1;i<=mask;i++) { sum[i]=sum[i-(i&-i)]+v[i&-i]; } f[0]=1; for(int i=1;i<=n;i++) { g[1<<(i-1)]=1; } for(int i=1;i<=mask;i++) { if(sum[i]>0) { for(int j=i^mask;j;j-=j&-j) { int k=j&-j; add(g[i|k],g[i]); } } else { for(int j=i;j;j-=j&-j) { int k=j&-j; add(f[i],f[i^k]); } } } for(int i=1;i<=mask;i++) { ans=(ans+1ll*g[i]*f[mask^i]%mod*sum[i]%mod)%mod; } printf("%d",(ans%mod+mod)%mod); }