【bzoj5161】最长上升子序列 状压dp+打表

题目描述

现在有一个长度为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;
}

 

posted @ 2018-03-21 20:19 GXZlegend 阅读(...) 评论(...) 编辑 收藏