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\n",i,f[i]);
        res=add(res,1ll*f[i]*(sum[i]+P)%P*g[lim^i]%P);
    }
    printf("%d\n",res);
    return 0;
}
posted @ 2019-01-20 18:39  bztMinamoto  阅读(190)  评论(3编辑  收藏  举报
Live2D