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 <bits/stdc++.h>
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;s<bin[n];++s)
            if(s&bin[i-1]) inc(sum[s],a[i]);
    }for(int s=0;s<bin[n];++s){
        if(sum[s]>0){
            for(int i=0;i<n;++i)
                if(!(s&bin[i])) inc(f[s|bin[i]],f[s]);continue;
        }for(int i=0;i<n;++i)
            if(s&bin[i]) inc(g[s],g[s^bin[i]]);
    }for(int s=0;s<bin[n];++s) inc(ans,(ll)f[s]*sum[s]%mod*g[bin[n]-1^s]%mod);
    printf("%d\n",ans<0?ans+mod:ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值