bzoj5369 [Pkusc2018]最大前缀和(状压dp)

考虑对于一个序列,如果 Sp S p 是最大前缀和,那么对于p+1…n这个序列,需要满足任意前缀和均<=0.否则 Sp S p 一定不是最大前缀和。

sum[s]表示s子集的点的权值和。
f[s],表示选了s的点,sum[s]为最大前缀和的方案数 g[s],表示选了s的点,所有前缀和均<=0的方案数
那么最后答案就是 sum[s]f[s]g[s¯¯¯] ∑ s u m [ s ] ∗ f [ s ] ∗ g [ s ¯ ]
f[s]的转移,如果sum[s]>0,可以考虑在满足f[s]的方案最前面放一个没出现过的数,那么一定可以转移到f[s|bin[i]]。
g[s]的转移,可以考虑在后面再放一个i,如果sum[s|bin[i]]<0那么就可以转移到g[s|bin[i]|
复杂度 O(2nn) O ( 2 n n )

#include 
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 25
#define mod 998244353
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int n,a[N],sum[1100000],f[1100000],g[1100000],bin[N],ans=0;
//f[s],选了s的点,sum[s]为最大前缀和的方案数 g[s],选了s的点,所有前缀和均<=0的方案数
inline void inc(int &x,int y){x+=y;x%=mod;}
int main(){
//  freopen("a.in","r",stdin);
    n=read();bin[0]=1;g[0]=1;
    for(int i=1;i<=n;++i) a[i]=read(),bin[i]=bin[i-1]<<1;
    for(int i=1;i<=n;++i){
        f[bin[i-1]]=1;
        for(int s=0;sif(s&bin[i-1]) inc(sum[s],a[i]);
    }for(int s=0;sif(sum[s]>0){
            for(int i=0;iif(!(s&bin[i])) inc(f[s|bin[i]],f[s]);continue;
        }for(int i=0;iif(s&bin[i]) inc(g[s],g[s^bin[i]]);
    }for(int s=0;s1^s]%mod);
    printf("%d\n",ans<0?ans+mod:ans);
    return 0;
}

你可能感兴趣的:(bzoj,状压dp)