1. 程式人生 > >bzoj 5369 最大字首和

bzoj 5369 最大字首和

Written with StackEdit.

Description

\(C\)是一個演算法競賽愛好者,有一天小\(C\)遇到了一個非常難的問題:求一個序列的最大子段和。

但是小\(C\)並不會做這個題,於是小\(C\)決定把序列隨機打亂,然後取序列的最大字首和作為答案。

\(C\)是一個非常有自知之明的人,他知道自己的演算法完全不對,所以並不關心正確率,他只關心求出的解的期望值,

現在請你幫他解決這個問題,由於答案可能非常複雜,所以你只需要輸出答案乘上\(n!\)後對\(998244353\)取模的值,顯然這是個整數。

Input

第一行一個正整數\(n\),表示序列長度。

第二行\(n\)個數,表示原序列\(a[1..n]\),第\(i\)個數表示\(a[i]\)

\(1≤n≤20,Sigma(|A_i|)<=10^9\),其中\(1<=i<=N.\)

Output

輸出一個非負整數,表示答案。

Sample Input

2
-1 2

Sample Output

3

Solution

  • 注意到\(n\)很小,每個子集的權值和我們可以暴力計算得出.
  • 直接考慮各個子集作為最大字首和.
  • 顯然,一個子集\(S\)排列後能成為最大字首和,那麼這個排列中不能有負的字尾和(否則去掉會更優),剩下的數排列後不能有正的字首和(否則加上會更優).
  • 我們令\(f[S]\)表示將\(S\)集合中的數排成沒有負的字尾和的排列的方案數,\(g[S]\)表示將\(S\)集合中的數排成沒有正的字首和的排列的方案.
  • 那麼易知答案即為\(\sum_{S\in U,sum[S]>=0}f[S]*g[\complement_{U}S]*sum[S]\).
  • 考慮如何快速計算出\(f\)\(g\).若對於一個集合\(i\),新增了一個數\(j\).(\(j\notin i\)).
  • 我們可以將\(i\)任意排列,再將\(j\)放在最後,方案數為\(f[i]\)\(g[i]\),統計入貢獻.每個集合中的每個數都會被放在最後轉移過來,所以總貢獻一定是正確的.
  • 這樣,只需要在加數的時候判斷一下\(sum[i]\)的符號,即可確定轉移\(f\)\(g\).
#include<bits/stdc++.h>
using namespace std;
typedef long long LoveLive;
inline int read()
{
    int out=0,fh=1;
    char jp=getchar();
    while ((jp>'9'||jp<'0')&&jp!='-')
        jp=getchar();
    if (jp=='-')
        {
            fh=-1;
            jp=getchar();
        }
    while (jp>='0'&&jp<='9')
        {
            out=out*10+jp-'0';
            jp=getchar();
        }
    return out*fh;
}
const int P=998244353;
const int MAXS=(1<<20)+10;
inline int add(int a,int b)
{
    return (a + b) % P;
} 
inline int mul(int a,int b)
{
    return 1LL * a * b % P;
}
int a[21];
int sum[MAXS],f[MAXS],g[MAXS];
int n;
inline int calc(int S)
{
    int res=0;
    for(int i=0;i<n && S;++i,S>>=1)
        if(S&1)
            res+=a[i];
    return res;
}
int main()
{
    n=read();
    for(int i=0;i<n;++i)
        a[i]=read();
    int S=1<<n;
    for(int i=0;i<S;++i)
        sum[i]=calc(i);
    for(int i=0;i<n;++i)
        f[1<<i]=1,g[1<<i]=1;
    for(int i=0;i<S;++i)
        {
            if(sum[i]>0)
                {
                    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]);
                }
        }
    int ans=0;
    int U=S-1;
    g[0]=1;
    for(int i=0;i<S;++i)
        if(sum[U^i]<=0)
            ans=add(ans,mul(mul(f[i],sum[i]),g[U^i]));
    printf("%d\n",add(ans,P));
    return 0;
}