[PKUSC2018]最大前缀和(状压DP)

题目大意:求给定的 $n$ 个数的所有排列的最大前缀和(不能为空)之和对 $10^9+7$ 取模的值。

$1\le n\le 20,1\le\sum|a_i|\le 10^9$。


神级DP。杂题选讲的神级毒瘤讲题人CDW讲的。

考虑一个集合 $S$ 能作为最大前缀和出现的方案数。(即贡献系数)

发现前 $|S|$ 个数满足最大前缀和是整个序列,后 $n-|S|$ 个数满足最大前缀和 $<0$。(虽然 $\le 0$ 也行,但为了避免重复统计就要 $<0$)

设 $f[S]$ 为在 $S$ 的所有排列中,最大前缀和 $<0$ 的个数。

设 $g[S]$ 为在 $S$ 的所有排列中,最大前缀和 $=sum[S]$ 的个数。($sum$ 是和)

$f[S]=\begin{cases}0&sum[S]\ge 0\\ \sum\limits_{i\in S}f[S-{i}]&sum[S]<0\end{cases}$

初始 $f[0]=1$。

解释一下,如果 $sum[S]\ge 0$,那么最大前缀和不会小于 $0$。否则枚举最后一个数,当且仅当前面的最大前缀和 $<0$ (或者前面没有数,所以 $f[0]=1$)且 $sum[S]<0$ 时才可以。第二个条件已经保证满足了。

$g[S]\rightarrow g[S+(1<<i)](sum[S]\ge 0,i\notin S)$

初始 $g[{i}]=1$。

解释一下,考虑从已有状态扩展,枚举在 $S$ 前加一个数 $i$,当且仅当 $S$ 最大前缀和是自己时,新序列最大前缀和才是自己。、

答案为 $\sum sum[S]g[S]f[U-S]$。

时间复杂度 $O(n2^n)$。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
    char ch=getchar();int x=0,f=0;
    while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    return f?-x:x;
}
int n,a[22],ans,f[1111111],g[1111111];
ll S[1111111];
int main(){
    n=read();
    FOR(i,0,n-1) a[i]=read(),g[1<<i]=1;
    FOR(i,0,(1<<n)-1) FOR(j,0,n-1) if((i>>j)&1) S[i]+=a[j];
    f[0]=1;
    FOR(i,0,(1<<n)-1){
        if(S[i]>=0){FOR(j,0,n-1) if(!((i>>j)&1)) g[i|(1<<j)]=(g[i|(1<<j)]+g[i])%mod;}
        else FOR(j,0,n-1) if((i>>j)&1) f[i]=(f[i]+f[i^(1<<j)])%mod;
    }
    FOR(i,0,(1<<n)-1) ans=(ans+1ll*(S[i]+mod)%mod*g[i]%mod*f[((1<<n)-1)^i])%mod;
    printf("%d\n",ans);
}
View Code

猜你喜欢

转载自www.cnblogs.com/1000Suns/p/10922069.html
今日推荐