题目大意
给你一个 nnn 个数的排列 PPP
两个人轮流在其中取数,按照以下规则
1、每个人取的数的下标大于之前自己所取数的下标
2、每个人取的数要大于之前两个人所取的数
问两个人所取数的个数的期望
解题思路
期望DP
定义 dp[i]dp[i]dp[i] 表示当前在位置 iii 还需要 dp[i]dp[i]dp[i] 的代价到达终点
dp[i]=vi+∑ci∗dp[j]dp[i] = v_i + \sum c_i * dp[j]dp[i]=vi+∑ci∗dp[j]
答案是 dp[0]dp[0]dp[0]
viv_ivi 是这一步的代价,jjj 是后继状态,cic_ici 是从状态 iii 到状态 jjj 的概率
在这个题中,vi=1v_i = 1vi=1 设 kkk 为后继状态的个数, ci=1/kc_i = 1 / kci=1/k
由于是两个人在走,所以我们开二维的 dp[i][j]dp[i][j]dp[i][j] ,表示第一个人在第 iii 个数,第二个人在第 jjj 的数。由于我们要遵循第二个规则,那么我们只需要比较 ai,aja_i, a_jai,aj 的大小就知道这一步轮到了谁走(小的这一步走)
- ai>aj:dp[i][j]=1+∑c1∗dp[i][k1] k1a_i > a_j: dp[i][j] = 1 + \sum c_1 * dp[i][k_1] \ k_1ai>aj:dp[i][j]=1+∑c1∗dp[i][k1] k1 是 iii 后继状态的下标
c1c_1c1 是 iii 后继状态的个数 - ai<aj:dp[i][j]=1+∑c2∗dp[k2][j] k2a_i < a_j: dp[i][j] = 1 + \sum c_2 * dp[k_2][j] \ k_2ai<aj:dp[i][j]=1+∑c2∗dp[k2][j] k2 是 jjj 后继状态的下标
c2c_2c2 是 jjj 后继状态的个数
这样转移复杂度是 O(n3)O(n^3)O(n3),我们考虑一下如何优化
我们发现 dp[i][k1] dp[k2][j]dp[i][k_1] \ \ \ dp[k_2][j]dp[i][k1] dp[k2][j] 是分别与 j ij \ ij i 无关的,所以我们用一个后缀和来统计一下这个求和式子
设 f[i]=∑dp[i][k1] g[j]=∑dp[k2][j]f[i] = \sum dp[i][k_1] \ \ g[j] = \sum dp[k_2][j]f[i]=∑dp[i][k1] g[j]=∑dp[k2][j] 对于 f[i]f[i]f[i] 我们需要满足的条件是 k1>i && a[k1]>a[i]k_1 > i \ \&\& \ a[k_1] > a[i]k1>i && a[k1]>a[i] 我们发现,在 g[j]g[j]g[j] 中,令 j=k1 k2=ij = k_1 \ k_2 =ij=k1 k2=i 恰好是满足这个条件的。
所以我们在算第二种情况时,将 f[i]f[i]f[i] 加上 dp[i][j]dp[i][j]dp[i][j],在算第一种情况时 g[i]g[i]g[i] 加上 dp[i][j]dp[i][j]dp[i][j]
最后统计答案,因为第一个人可以任意选,我们要求的是 1/n∗∑dp[i][0]1/n * \sum dp[i][0]1/n∗∑dp[i][0]
Code
#include <bits/stdc++.h>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define pb push_back
using namespace std;
const int MAXN = 5e3 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 998244353;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n;
ll dp[MAXN][MAXN];
ll inv[MAXN], f[MAXN], g[MAXN];
ll c1[MAXN], c2[MAXN];
ll a[MAXN];
ll qpow(ll x, ll y){
ll ret = 1;
while(y){
if(y&1)
ret = 1ll * ret * x % mod;
x = 1ll * x * x % mod;
y >>= 1;
}
return ret;
}
void solve(){
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
inv[i] = qpow(i, mod-2);
}
for(int i = n; i >= 0; --i){
for(int j = n; j >= 0; --j){
if(a[i] > a[j]){
dp[i][j] = (1 + inv[c1[i]] * f[i] % mod) % mod;
g[j] = (g[j] + dp[i][j]) % mod;
c2[j]++;
}
else if(a[i] < a[j]){
dp[i][j] = (1 + inv[c2[j]] * g[j] % mod) % mod;
f[i] = (f[i] + dp[i][j]) % mod;
c1[i]++;
}
}
}
ll ans = 0;
for(int i = 1; i <= n; i++)
ans = (ans + inv[n] * dp[i][0] % mod) % mod;
cout << ans << endl;
}
int main()
{
#ifdef ONLINE_JUDGE
#else
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
qc;
int T;
// cin >> T;
T = 1;
while(T--){
solve();
}
return 0;
}
本文介绍了一道关于两人轮流取数问题的解法,利用期望DP求解,通过定义状态转移方程和后缀和优化计算复杂度。核心内容包括规则阐述、动态规划模型和代码实现。
319

被折叠的 条评论
为什么被折叠?



