1. 程式人生 > >BZOJ5369 [Pkusc2018]最大字首和

BZOJ5369 [Pkusc2018]最大字首和

題意

小C是一個演算法競賽愛好者,有一天小C遇到了一個非常難的問題:求一個序列的最大子段和。
但是小C並不會做這個題,於是小C決定把序列隨機打亂,然後取序列的最大字首和作為答案。
小C是一個非常有自知之明的人,他知道自己的演算法完全不對,所以並不關心正確率,他只關心求出的解的期望值,
現在請你幫他解決這個問題,由於答案可能非常複雜,所以你只需要輸出答案乘上n!後對998244353取模的值,顯然這是個整數。
注:最大字首和的定義:i∈[1,n],Sigma(aj)的最大值,其中1<=j<=i

\(n \leq 20\)

分析

乘上\(n!\),所謂期望其實就是每種最大字首和乘上方案數的乘積的和。

參照Boss.Pi的題解。

看資料範圍,考慮狀壓dp。注意到字首和的取值只有 \(2^n\) 種.

然後可以列舉每一個集合的元素當最大字首和 , 那麼這個集合的元素排列之後每一個字尾都必須大於 \(0\) , 且這個集合的補集排列之後必須保證每一個字首和都小於 \(0\).

那麼狀壓 DP 就行了 , 設 \(f[i]\) 表示集合 \(i\) 作為最大字首和且排列之後每個字尾都大於 \(0\) 的方案數 , \(g[i]\) 表示集合 \(i\) 中元素排列之後每個字首都小於 \(0\) 的方案數.

強制 \(f,g\) 必須在合法的時候才能轉移就行了.

時間複雜度\(O(n 2^n)\)

程式碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<algorithm>
#include<bitset>
#include<cassert>
#include<ctime>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read()
{
    rg T data=0;
    rg int w=1;
    rg char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            w=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        data=data*10+ch-'0';
        ch=getchar();
    }
    return data*w;
}
template<class T>T read(T&x)
{
    return x=read<T>();
}
using namespace std;
typedef long long ll;

co int N=22,mod=998244353;
int n;
int a[N];
int sum[1<<N],f[1<<N],g[1<<N];

il int add(int x,int y)
{
    return (x+y)%mod;
}

il int mul(int x,int y)
{
    return (ll)x*y%mod;
}

int main()
{
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
    read(n);
    for(int i=0;i<n;++i)
        read(sum[1<<i]);
#define lowbit(x) (x&-x)
    for(int i=0;i<(1<<n);++i)
        sum[i]=sum[i-lowbit(i)]+sum[lowbit(i)];
    for(int i=0;i<n;++i)
        f[1<<i]=1,g[1<<i]=1;
    for(int i=0;i<(1<<n);++i)
    {
        if(sum[i]>0)
        { // edit 1: the big brace is important
            for(int j=0;j<n;++j)
                if(~i>>j&1)
                    f[i|(1<<j)]=add(f[i|(1<<j)],f[i]);
        }   
        else
        {
            for(int j=0;j<n;++j)
                if(~i>>j&1)
                    g[i|(1<<j)]=add(g[i|(1<<j)],g[i]);
        }
    }
    g[0]=1;
    int ans=0;
    for(int i=0;i<(1<<n);++i)
        if(sum[((1<<n)-1)^i]<=0)
            ans=add(ans,mul(f[i],mul(sum[i],g[((1<<n)-1)^i])));
    printf("%d",(ans+mod)%mod);
    return 0;
}

Hint

大括號的問題,害得我交了23次。