题目大意:
求长度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(3n∗n3)
考虑优化状态,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(2n∗n2)
有个神仙的杨图做法,太菜了,不会,知乎上有,传送门.
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);
}