1. 程式人生 > 其它 >【洛谷P5369】最大字首和

【洛谷P5369】最大字首和

題目

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

\(\sum_{j=1}^{i}a_j\) 的最大值。
\(n\leq 20\)

思路

\(sum[s]\) 表示集合 \(s\) 中所有元素之和。設 \(f[s]\) 表示集合為 \(s\) 時,最大字首和恰好為 \(sum[s]\) 的方案數。\(g[s]\) 表示集合為 \(s\) 時,最大字首和為 \(0\) 的方案數。
那麼答案顯然就是

\[\sum^{2^n-1}_{i=1}sum[i]\times f[i]\times g[2^n-1-i] \]

接下來考慮轉移。
如果 \(sum[s]\geq 0\),那麼我們可以通過在集合 \(s\) 前加入一個數字 \(i\) 來轉移到 \(f[s\ \rm xor\ i]\)

。否則無法對 \(f[s\ \rm xor\ i]\) 產生貢獻。
如果 \(sum[s]<0\),那麼對於一個在 \(s\) 裡的數 \(i\)\(g[s]\) 可以通過 \(g[s\ \rm xor\ i]\) 轉移過來。
時間複雜度 \(O(2^nn)\)

程式碼

#include <bits/stdc++.h>
using namespace std;

const int N=25,M=(1<<20),MOD=998244353;
int n,ans,lim,a[N],f[M],g[M],sum[M];

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]),f[1<<i-1]=1;
	g[0]=1; lim=1<<n;
	for (int s=1;s<lim;s++)
	{
		for (int i=1;i<=n;i++)
			if (s&(1<<i-1)) sum[s]+=a[i];
		if (sum[s]<0)
			for (int i=1;i<=n;i++)
				if (s&(1<<i-1)) g[s]=(g[s]+g[s^(1<<i-1)])%MOD;
		if (sum[s]>=0)
			for (int i=1;i<=n;i++)
				if (!(s&(1<<i-1))) f[s|(1<<i-1)]=(f[s|(1<<i-1)]+f[s])%MOD;
	}
	for (int i=0;i<lim;i++)
		ans=(ans+1LL*sum[i]%MOD*f[i]%MOD*g[(lim-1)^i])%MOD;
	cout<<(ans%MOD+MOD)%MOD;
	return 0;
}