1. 程式人生 > >【bzoj5161】最長上升子序列 狀壓dp+打表

【bzoj5161】最長上升子序列 狀壓dp+打表

-s 只需要 [] sca div limits pow 證明 AC

題目描述

現在有一個長度為n的隨機排列,求它的最長上升子序列長度的期望。 為了避免精度誤差,你只需要輸出答案模998244353的余數。

輸入

輸入只包含一個正整數n。N<=28

輸出

輸出只包含一個非負整數,表示答案模998244353的余數。 可以證明,答案一定為有理數,設其為a/b(a、b為互質的整數),你輸出的整數為x, 則你需要保證0≤x<998244353且a與bx模998244353同余。

樣例輸入

2

樣例輸出

499122178


題解

狀壓dp+打表

套路:對於排列問題,從左到右處理比較困難的話,考慮從小到大把數插入來處理。

對於一個確定的 $1\sim n$ 的排列,令 $f[i]$ 表示該排列以第 $i$ 個數結尾的最長上升子序列長度。令 $mx[i]$ 表示其前綴最大值,顯然 $mx[i]\le mx[i+1]\le mx[i]+1$ ,根據這個我們可以狀壓前綴最大值的差分數組。

考慮在 $i$ 位置和 $i+1$ 位置加入一個新的最大數:這個數結尾的最長上升子序列長度一定為 $mx[i]+1$ ,因此把該位改成1,這個數後面的第一個1受其影響差分數組-1,把它改成0。

設 $dp[i][j]$ 表示 $1\sim i$ 的排列,$mx$ 的差分數組狀壓後為 $j$ 的方案數。那麽答案就是 $\sum 每種狀態的數目\times 每種狀態的最長上升子序列長度$ 。

代碼中我沒有狀壓 $mx[1]-mx[0]$ ,因為一定是 $1$ 。這樣答案就是 $\sum\limits_{i=0}^{2^{n-1}-1}dp[n][i]·cnt[i]$ ,$cnt[i]$ 表示 $i$ 種 $1$ 的數目。

最後乘以階乘的逆元即為期望。

時間復雜度 $O(n^2·2^n)$ ,過不去。怎麽辦?打表...

打表程序:

#include <cstdio>
#include <cstring>
#define mod 998244353
typedef long long ll;
int f[2][134217735] , cnt[134217735];
ll pow(ll x , int y)
{
	ll ans = 1;
	while(y)
	{
		if(y & 1) ans = ans * x % mod;
		x = x * x % mod , y >>= 1;
	}
	return ans;
}
int main()
{
	int n , i , j , k , d , t , pos;
	ll ans = 0 , fac = 1;
	scanf("%d" , &n) , n -- ;
	f[0][0] = 1;
	for(d = i = 1 ; i <= n ; i ++ , d ^= 1)
	{
		memset(f[d] , 0 , sizeof(int) * (1 << i));
		for(j = 0 ; j < (1 << (i - 1)) ; j ++ )
		{
			f[d][j << 1] = (f[d][j << 1] + f[d ^ 1][j]) % mod , pos = -1;
			for(k = i - 1 ; ~k ; k -- )
			{
				t = ((j >> k) << (k + 1)) | (1 << k) | (j & ((1 << k) - 1));
				if(j & (1 << k)) pos = k;
				if(~pos) t ^= (1 << (pos + 1));
				f[d][t] = (f[d][t] + f[d ^ 1][j]) % mod;
			}
		}
	}
	for(i = 1 ; i < (1 << n) ; i ++ ) cnt[i] = cnt[i - (i & -i)] + 1;
	for(i = 0 ; i < (1 << n) ; i ++ ) ans = (ans + 1ll * f[n & 1][i] * (cnt[i] + 1)) % mod;
	for(i = 1 ; i <= n + 1 ; i ++ ) fac = fac * i % mod;
	printf("%lld\n" , ans * pow(fac , mod - 2) % mod);
	return 0;
}

AC程序:

#include <cstdio>
int a[]={1,499122178,2,915057326,540715694,946945688,422867403,451091574,317868537,200489273, 976705134,705376344,662845575,331522185,228644314,262819964,686801362,495111839,947040129,414835038,696340671,749077581,301075008,314644758,102117126,819818153,273498600,267588741},n;
int main()
{
    scanf("%d",&n);
    printf("%d",a[n-1]);
    return 0;
}

【bzoj5161】最長上升子序列 狀壓dp+打表