loj#6433. 「PKUSC2018」最大前缀和(状压dp)

传送门

今天(PKUWC)试机的题

看着边上的大佬们一个个(A)穿咱还是不会……

我们考虑枚举最大前缀和,如果一个前缀(1)(p)是最大前缀和,那么(p)后面的所有前缀和都要小于(0)

于是我们设(sum_S)为子集(S)中所有元素的和,(f_S)为满足最大前缀和为(sum_S)(S)的排列个数,那么我们可以枚举这个排列中位于第一个的数,只要剩下的数之和(sum_{S-{x}})大于(0),那么(f_S)就可以加上(f_{S-{x}})

然后设(g_S)为满足最大前缀和小于(0)(S)的排列个数,和上面差不多,只要枚举位于最后的元素是哪个就行了

//minamoto
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(R int i=a,I=b-1;i>I;--i)
inline int max(const R int &x,const R int &y){return x>y?x:y;}
inline int min(const R int &x,const R int &y){return x<y?x:y;}
using namespace std;
char buf[1<<21],*p1=buf,*p2=buf;
inline char getc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
int read(){
    R int res=1,f=1;R char ch;
    while((ch=getc())>'9'||ch<'0')(ch=='-')&&(f=-1);
    for(res=ch-'0';(ch=getc())>='0'&&ch<='9';res=res*10+ch-'0');
    return res*f;
}
const int N=(1<<20)+5,P=998244353;
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int dec(R int x,R int y){return x-y<0?x-y+P:x-y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
int ksm(R int x,R int y){
    R int res=1;
    for(;y;y>>=1,x=mul(x,x))if(y&1)res=mul(res,x);
    return res;
}
int f[N],g[N],sum[N],a[N];
int n,res,lim;
int main(){
    n=read(),lim=(1<<n)-1;
    fp(i,0,n-1)a[1<<i]=read();
    fp(i,1,lim)sum[i]=sum[i^(i&-i)]+a[i&-i];
    g[0]=1;
    fp(i,0,lim)if(sum[i]<=0){
        R int j=i;
        while(j)g[i]=add(g[i],g[i^(j&-j)]),j^=j&-j;
    }
    fp(i,0,n-1)f[1<<i]=1;
    fp(i,0,lim){
        if(sum[i]>0){
            R int j=lim^i;
            while(j)f[i|(j&-j)]=add(f[i|(j&-j)],f[i]),j^=j&-j;
        }
        // printf("%d %d
",i,f[i]);
        res=add(res,1ll*f[i]*(sum[i]+P)%P*g[lim^i]%P);
    }
    printf("%d
",res);
    return 0;
}
原文地址:https://www.cnblogs.com/bztMinamoto/p/10295748.html