zoukankan      html  css  js  c++  java
  • 【bzoj5161】最长上升子序列 状压dp+打表

    题目描述

    现在有一个长度为n的随机排列,求它的最长上升子序列长度的期望。
    为了避免精度误差,你只需要输出答案模998244353的余数。

    输入

    输入只包含一个正整数n。N<=28

    输出

    输出只包含一个非负整数,表示答案模998244353的余数。
    可以证明,答案一定为有理数,设其为a/b(a、b为互质的整数),你输出的整数为x,
    则你需要保证0≤x<998244353且a与bx模998244353同余。

    样例输入

    2

    样例输出

    499122178


    题解

    状压dp+打表

    套路:对于排列问题,从左到右处理比较困难的话,考虑从小到大把数插入来处理。

    对于一个确定的 $1sim 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]$ 表示 $1sim i$ 的排列,$mx$ 的差分数组状压后为 $j$ 的方案数。那么答案就是 $sum 每种状态的数目 imes 每种状态的最长上升子序列长度$ 。

    代码中我没有状压 $mx[1]-mx[0]$ ,因为一定是 $1$ 。这样答案就是 $sumlimits_{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
    " , 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;
    }
    
  • 相关阅读:
    开源:不断创新的动力
    Inkpad中文翻译已合并到官方项目
    Inkpad绘图原理浅析
    Vectoroid
    发布大幅重构优化的 TouchVG 1.0.2
    清理掉一直想研究的开源项目
    函数指针调用方式
    音视频直播优化
    std::unique_lock与std::lock_guard区别示例
    c++容器的操作方法总结
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/8619335.html
Copyright © 2011-2022 走看看