【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; }