2021牛客多校 I 期望dp

在这里插入图片描述
在这里插入图片描述

题意

A l i c e Alice Alice B o b Bob Bob 玩游戏,规定每轮每个人在序列中选一个数字,要求是选择的数字的大小一定是比之前两人选过的数字都要大,选择数字在序列中的下标要比该选手选过的数字下标要大。

题解

我们发现该选手选择序列的限制有两条,我们想到是否可以使用dp来表示限制然后转台转移模拟选择的数字,然后我们开始设置状态, f [ i ] [ j ] f[i][j] f[i][j]表示Alice选手前一次选择了数字 j j j i i i则表示 B o b Bob Bob选手上一次选择的数字的期望次数 g [ i ] [ j ] g[i][j] g[i][j]同上,只不过是转换了角色,我们发现这样复杂度有点高, ( N 3 ) (N^3) (N3),一般来说我们可以寻求各种优化,但是想了好久发现怎样优化都是不好做,但是我们发现 A l i c e Alice Alice B o b Bob Bob是等价的,考虑换一种状态表示, d p [ i ] [ j ] dp[i][j] dp[i][j]表示的是上一个人选择数字 i i i,然后该人选择数字 j j j到达最终状态的期望步数,Q:为什么要是到达最终状态的期望步数呢?这样是为了我们后边对其进行优化,假设我们设置是到达该状态的期望步数,那么我们的状态转移就变成了, d p [ i ] [ j ] = ( d p [ k ] [ i ] + 1 ) / c n t dp[i][j] = (dp[k][i] + 1) / cnt dp[i][j]=(dp[k][i]+1)/cnt,cnt表示的是上一个选手的可选择数量,我们发现这样向后转移是很难优化的,只是计算cnt大小就已经很难。我们于是开始考虑状态从后向前更新,于是状态表示就变成了"到达最终状态的期望步数"。我们开一个数组 p o s pos pos表示对给定序列的映射,我们开始在pos数组上进行dp,考虑状态转移, d p [ i ] [ j ] = ( d p [ j ] [ k ] + 1 ) / c n t dp[i][j] = (dp[j][k] + 1)/ cnt dp[i][j]=(dp[j][k]+1)/cnt,这样我们计算cnt就可以用一个前缀数组来维护。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <ctime>
#include <vector>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <cmath>


using namespace std;
#define x first 
#define y second 

typedef long long LL;
typedef pair <int, int> PII;

const int N = 5e3 + 10, INF = 0x3f3f3f3f, mod = 998244353;

int dp[N][N], a[N], p[N], unf[N];
int main()
{
	clock_t c1 = clock();
	ios::sync_with_stdio(false);
	cin.tie(0);
#ifdef LOCAL
	freopen("in.in", "r", stdin);
	freopen("out.out", "w", stdout);
#endif
	// ------------------------------------------------------------------------------------------------>
		int n; cin >> n;
		for (int i = 1; i <= n; i ++) cin >> a[i];
		for (int i = 1; i <= n; i ++) p[a[i]] = i;
		unf[1] = unf[0] = 1;
		for (int i = 2; i <= n; i ++) unf[i] = (LL)(mod - mod / i) * unf[mod % i] % mod;
		for (int i = n; i >= 1; i --)
		{
			vector <int> sum (n + 5, 0), cnt (n + 5, 0);
			for (int j = i + 1; j <= n; j ++)
			{
				cnt[p[j]] ++;
				sum[p[j]] = dp[i][j];
			}
			for (int j = n; j >= 0; j --) 
			{
				cnt[j] += cnt[j + 1];
				sum[j] = ((LL)sum[j + 1] + sum[j]) % mod;
			}
			for (int j = 0; j < i; j ++) 
			{
				if(cnt[p[j]]) dp[j][i] = ((LL)sum[p[j]] * unf[cnt[p[j]]] % mod + 1) % mod;
			}
		}
		LL ans = 0;
		for (int i = 1; i <= n; i ++) ans = (ans + dp[0][i]) % mod;
		ans = (ans * unf[n] % mod + 1) % mod;
		cout << ans << '\n';
	// ------------------------------------------------------------------------------------------------>
end:
	cerr << "Time Use  -----> " << clock() - c1 << "ms" << endl;
	return 0;// 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值