[牛客多校]第一场 I. Increasing Subsequence

题意: 给定一个\(1~n\)的排列,两个人分别选数,每个人这次选的数必须比自己上次选的位置要靠后,同时必须比两人之前选的所有数都要大,如果有多个满足条件的数,他们就会等概率选取一个。最开始的那个人可以等概率选取任何一个数。求最终选取的数的期望个数。
范围: \(n\le 5000\)
题解: 有两个限制条件。第一,自己选的位置递增,第二,选的数字比之前所有数字都要大。

比较naive的想法是首先干掉第一个条件,于是设计出dp状态:\(f[i][j][0/1]\)表示第一个人上一次选了i位置的数,第二个人选了j位置的数,这一次轮到哪个人选的概率。
一个(我自己)比较想不到的点是:一个局面变换的代价是\(1\)(走一步),那么每个局面对答案的贡献是它自己的概率乘\(1\)
枚举递增的位置,并且满足数值递增进行转移,这个枚举不太好节省,时间是\(O(n^3)\)(见code1)。

那么,如何设计一个更加好的dp方案呢?
注意到DP本质是在一个DAG上往后走的过程,我们可以定义出一个势能,而所有DP都是往势能降低的方向进行的。
在这道题上,我们发现,有两个势能可以选择:

  1. 每个人的位置,就是上面那个DP方法,但是这个势能是在一个二维平面上单减的,有两个维度要考虑,所以十分复杂。
  2. 选取的最大数字,这个就十分的amazing啊,你可以发现,这个数字是一维的,也就是说,假如DP顺序按照这个数字递减,就可以设计出一个比较优秀的方案了。

不过这种方法也有个局限性,当数字并不是两两不同,并且选取不一定要单调的时候,就无法进行dp,因为这时候势能不是随着这个数字递减的(可以选相同数字),这时候只能通过升维进行DP了。

我们可以让\(f[i][j]\)表示上个人选了\(i\),上上个人选了\(j\)
这时候的势能为\(i\),并且满足\(i<j\)
我们往势能降低的方向进行dp,我们可以从\(f[i][j]\)转移到\(f[k][i](k<i)\)
转移的时候,我们需要知道有\(i+1~n\)多少个数在\(k\)的后面。
记一个数的位置为\(p[x]\),有\(cnt\)个数。

\[f[k][i] = \sum_{j = i + 1} ^ n [p[j] > p[k]]\times f[i][j] / cnt \]

后面这个式子只有\([p[j] > p[k]], cnt\)\(k\)是有关的,这个相关性是“所有在\(k\)后面的数”,就是一个前缀和,所以我们对每个大于\(i\)的值,按照位置维护一个前缀和,就可以快速搞出这个值了。

最终势能降为0的位置就是答案,即\(\sum_{i = 1} ^ {n} f[0][i]\),见code2。
当然最开始\(n\)个数任意选,所以答案还要除一个\(n\)

code1

#include <bits/stdc++.h>
#define pt(x) cout << x << endl;
#define Mid ((l + r) / 2)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
int read() {
	char c; int num, f = 1;
	while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0';
	while(c = getchar(), isdigit(c)) num = num * 10 + c - '0';
	return f * num;
}
const int mod = 998244353;
const int N = 5009;
int n, a[N], f[N][N][2], p[N][N][2], inv[N];
int sum[N];
int tot[N][N];
signed main()
{
	memset(p, 0, sizeof(p));
	inv[0] = inv[1] = 1;
	for(int i = 2; i < N; i++) inv[i] = (1ll * mod - mod / i) * inv[mod % i] % mod;
	n = read();
	for(int i = 1; i <= n; i++) a[i] = read();
	for(int i = 1; i <= n; i++) 
		for(int j = n; j >= 1; j--) 
			tot[i][j] = tot[i][j + 1] + (a[j] > a[i]);
	for(int i = 1; i <= n; i++) p[i][0][0] = inv[n];
	for(int i = 1; i <= n; i++) {
		int cnt = 0;
		memset(sum, 0, sizeof(sum));
		for(int j = 1; j <= n; j++) {
			
		}
		for(int j = 1; j <= n; j++) {
			for(int k = 0; k < j; k++) if(a[j] > max(a[k], a[i])){
				int cnt = 0;
				if(a[k] > a[i]) cnt = tot[k][k + 1];
				else cnt = tot[i][k + 1];
				p[i][j][1] = (p[i][j][1] + 1ll * p[i][k][0] * inv[cnt] % mod) % mod;
			}
			for(int k = 0; k < i; k++) if(a[i] > max(a[k], a[j])){
				int cnt = 0;
				if(a[k] > a[j]) cnt = tot[k][k + 1];
				else cnt = tot[j][k + 1];
				p[i][j][0] = (p[i][j][0] + 1ll * p[k][j][1] * inv[cnt] % mod) % mod;
			}
		}
	}
	int ans = 0;
	for(int i = 0; i <= n; i++) {
		for(int j = 0; j <= n; j++) {
			if(p[i][j][1] != -1) ans = (ans + p[i][j][1]) % mod;
			if(p[i][j][0] != -1) ans = (ans + p[i][j][0]) % mod;
		}
	}
	printf("%d\n", ans);
	return 0;
}

code2

#include <bits/stdc++.h>
#define pt(x) cout << x << endl;
#define Mid ((l + r) / 2)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
int read() {
	char c; int num, f = 1;
	while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0';
	while(c = getchar(), isdigit(c)) num = num * 10 + c - '0';
	return f * num;
}
const int mod = 998244353;
const int N = 5009;
int n, a[N], f[N][N], p[N], inv[N];
int sum[N], cnt[N];
int tot[N][N];
signed main()
{
	memset(p, 0, sizeof(p));
	inv[0] = inv[1] = 1;
	for(int i = 2; i < N; i++) inv[i] = (1ll * mod - mod / i) * inv[mod % i] % mod;
	n = read();
	for(int i = 1; i <= n; i++) p[read()] = i;
	for(int i = n - 1; ~i; i--) {
		memset(cnt, 0, sizeof(int) * (n + 5));
		memset(sum, 0, sizeof(int) * (n + 5));
		for(int j = i + 1; j <= n; j++) {
			cnt[p[j]]++;
			sum[p[j]] = (sum[p[j]] + f[i][j]) % mod;
		}
		for(int j = n - 1; ~j; j--) cnt[j] += cnt[j + 1], sum[j] = (sum[j] + sum[j + 1]) % mod;
		for(int j = 0; j != i; j++) {
			if(cnt[p[j]])
				f[j][i] = (1ll 	* sum[p[j]] * inv[cnt[p[j]]] % mod + 1) % mod;
		}
	}
	int ans = 0;
	for(int i = 1; i <= n; i++) ans = (ans + f[0][i]) % mod;
	printf("%lld\n", (1ll * ans * inv[n] + 1) % mod);
	return 0;
}
posted @ 2021-09-17 09:18  _onglu  阅读(29)  评论(0编辑  收藏  举报