JZOJ 5929. 【NOIP2018模拟10.26】情书(n的排列LIS期望长度)

题目大意:

求长度n的排列的LIS的期望长度。

1<=n<=29

题答。

题解:

以前有个经典的做法,就是dp套dp。

考虑正常做LIS是怎么做的,有一个辅助数组l[i]表示长度为i的上升子序列的结尾最小是多少。

那么很容易想到设 f i , j f_{i,j} fi,j表示确定了排列的前i个,S是一个压缩状态,对于每一个数,0表示没有出现过,1表示出现了,但是不在l里,2表示出现了,且在l中。

算上转移应该得是 O ( 3 n ∗ n 3 ) O(3^n*n^3) O(3nn3)

考虑优化状态,dp过程中,我们并不用知道具体是哪些数被选了,而是考虑相对大小关系,那显然相对大小关系就是1-i的一个排列,然后每次转移就相当于插入一个1-i+1的数,把大于等于等于它的数+1,那么S就是 O ( 2 n ) O(2^n) O(2n),复杂度为 O ( 2 n ∗ n 2 ) O(2^n*n^2) O(2nn2)

有个神仙的杨图做法,太菜了,不会,知乎上有,传送门.
Code:

#include<cstdio>
#define ll long long
#define fo(i, x, y) for(int i = x, b = y; i <= b; i ++)
#define low(a) ((a) & -(a))
using namespace std;

const int mo = 998244353;

ll ksm(ll x, ll y) {
	if(y < 0) x = ksm(x, mo - 2), y = -y;
    ll s = 1;
    for(; y; y /= 2, x = x * x % mo)
        if(y & 1) s = s * x % mo;
    return s;
}

const int N = 31;

int n, f[2][1 << 24], l[N], o, a2[N], p[N], q[N];
ll s;

int main() {
	freopen("B.in", "r", stdin);
	freopen("B.out", "w", stdout);
	scanf("%d", &n);
	if(n == 24) {
		printf("301075008\n"); return 0;
	}
	if(n == 26) {
		printf("102117126\n"); return 0;
	}
	if(n == 28) {
		printf("273498600\n"); return 0;
	}
	if(n == 30) {
		printf("291085523\n"); return 0;
	}
	n --;
	a2[0] = 1; fo(i, 1, 30) a2[i] = a2[i - 1] * 2;
	fo(i, 0, 29) q[i] = a2[30] - a2[i], p[i] = a2[i] - 1;
	f[o][0] = 1;
	fo(i, 1, n) {
		fo(j, 0, a2[i] - 1) f[!o][j] = 0;
		fo(j, 0, a2[i - 1] - 1) {
			fo(k, 0, i - 1) {
				int nj = ((((j & q[k]) << 1) - low((j & q[k]) << 1)) | (j & p[k])) + a2[k];
				f[!o][nj] += f[o][j];
				f[!o][nj] > mo ? f[!o][nj] -= mo : 0;
			}
		}
		o = !o;
	}
	fo(i, 0, (1 << n) - 1) fo(j, 0, n - 1) s += (i >> j & 1) * f[o][i];
	s %= mo;
	fo(i, 2, n) s = s * ksm(i, mo - 2) % mo;
	printf("%lld", s);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值