LOJ P6433 BZOJ P5369「PKUSC2018」最大前缀和【状态压缩】

O r z O r z 想了几天都只想到一半,看大佬们的题解(看完大佬的题解我又要开始转变代码风格了)还看了很久,如果我现场做估计已经跪掉了。

首先,考虑对于答案的计算。

m a x i 表示第 i 种排列的最大前缀和,由于一共有 n ! 个排列,所以最后的答案就应该为:

A n s = ( m a x 1 n ! + m a x 2 n ! + . . . + m a x n ! n ! ) n !

A n s = i = 1 n ! m a x i n ! n !

A n s = i = 1 n ! m a x i

所以我们最后的结果其实就是所有排列的最大前缀和之和。

接着,我们来考虑对于每一个排列,最大前缀和出现的位置。假设最大前缀和的前缀的最后一个数的位置为 p ,那么一定 p 之后的数的前缀之和都要 < 0 ,不然就可以替换掉 p 使得最大前缀和可以更大。所以我们就把每一个排列分为两个部分来考虑,分割点就是 p

S u m [ i ] 表示 i 这个状态的数值之和。

f [ i ] 表示在 i 这个状态下最大前缀和为 S u m [ i ] 的方案数,转移为:

j i S u m [ i ] > 0 f [ i ] > f [ i + j ]

g [ i ] 表示在 i 这个状态下的最大前缀和都 0 的方案数,转移为:

j i S u m [ i + j ] <= 0 f [ i ] > f [ i + j ]

于是我们就得到了最后的答案:

A n s = i = 0 m a x S t a t e S u m [ i ] f [ i ] g [ m a x S t a t e i ]

#include <bits/stdc++.h>
#define For(I,L,R) for(I=(L);I<=(R);I++)
using namespace std;

inline int Read(){
    int X=0;char CH=getchar();bool F=0;
    while(CH>'9'||CH<'0'){if(CH=='-')F=1;CH=getchar();}
    while(CH>='0'&&CH<='9'){X=(X<<1)+(X<<3)+CH-'0';CH=getchar();}
    return F?-X:X;
}

const int Mod=998244353;
int F[1<<20],G[1<<20];
int N,T,Ans,A[1<<20],Sum[1<<20];

#define LowBit(X) (X&(-X))
#define Add(X,Y) if(((X)+=(Y))>=Mod) (X)-=Mod

int main(){
    int I,J,K;

    N=Read();T=(1<<N)-1;
    For(I,0,N-1) F[1<<I]=1,A[1<<I]=Read();

    For(I,0,T) Sum[I]=Sum[I^LowBit(I)]+A[LowBit(I)];

    For(I,0,T) if(Sum[I]>0)
        For(J,0,N-1) if(!((I>>J)&1))
            Add(F[I|(1<<J)],F[I]);

    G[0]=1;
    For(I,0,T) if(Sum[I]<=0)
        For(J,0,N-1) if((I>>J)&1)
            Add(G[I],G[I^(1<<J)]);

    For(I,0,T)
        Add(Ans,(1ll*(Sum[I]+Mod)*F[I]%Mod*G[T^I]%Mod));

    printf("%d\n",Ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/yanzhenhuai/article/details/81628225
今日推荐