luoguP5369 [PKUSC2018] 最大字首和

gmh77發表於2024-08-29

題目

n<=20

題解

想了半天3位狀態的折半,然後發現空間開不下(時間也不太行)

所以放棄思考,直接列舉答案


答案是a中的一個集合,設為S;記集合S的和為sum[S]

考慮當S確定時,有多少種方案能使答案恰好為sum[S]。為了處理多種sum相同的情況,記S為從前往後考慮,第一次出現最大ans的集合;記剩餘部分為\(\bar S\)

那麼考慮S和\(\bar S\)在序列上的情況,記前者組成的子段為L後者為R;
有L中任意真字尾的和>0,R中任意字首的和<=0(否則S會變)

所以取到S的情況就是二者方案乘積,都可以用\(O(n2^n)\)狀壓不斷加數求解(真字尾要特殊處理最後的全集,開兩個狀態搞)

code

#include <bits/stdc++.h>
#define fo(a,b,c) for (int a=b; a<=c; a++)
#define fd(a,b,c) for (int a=b; a>=c; a--)
#define add(a,b) a=((a)+(b))%mod
#define mod 998244353
#define Mod 998244351
#define ll long long
#define file
using namespace std;

const int N=21;
const int twoN=1048576;
int T,n,K=11;
int a[N];
int sum[twoN];
int F[twoN],f[twoN],g[twoN];
ll ans;

int main()
{
//	freopen("luogu5369.in","r",stdin);
	
	scanf("%d",&n);
	fo(i,1,n) scanf("%d",&a[i]);
	fo(s,1,(1<<n)-1)
	{
		fo(i,1,n)
		if (s&(1<<(i-1)))
		{
			sum[s]=sum[s-(1<<(i-1))]+a[i];
			break;
		}
	}
	
	F[0]=1;
	fo(s,0,(1<<n)-1-1)
	{
		fo(i,1,n)
		if (!(s&(1<<(i-1))))
		{
			if (sum[s|(1<<(i-1))]>0)
			add(F[s|(1<<(i-1))],F[s]);
			add(f[s|(1<<(i-1))],F[s]);
		}
	}
	g[0]=1;
	fo(s,0,(1<<n)-1-1)
	{
		fo(i,1,n)
		if (!(s&(1<<(i-1))) && sum[s|(1<<(i-1))]<=0)
		add(g[s|(1<<(i-1))],g[s]);
	}
	
	fo(s,1,(1<<n)-1)
	{
		int s2=((1<<n)-1)^s;
		add(ans,1ll*f[s]*g[s2]%mod*sum[s]);
	}
	printf("%lld\n",(ans+mod)%mod);
}

相關文章