题意:
一个长为的排列,有两个人轮流从中取数,每一轮中所取的数需要满足如下规则:
- 取的数大于之前所有人取的数
- 取的数的下标大于取数人之前取的数的下标
- 如果有多种取数方案,则随机进行取数
- 第一个人第一次取数随机
- 如果有人没法再取数,游戏结束
求最后期望能取数多少次。
Solution:
期望DP,比赛时最后想到了做法但是没写完,晚上调了调就过了。
首先想到一个状态:表示当前轮选择了第
个位置,前一轮选择了第
个位置时,游戏期望还能进行的轮数(包括当前轮)。
现在我们考虑这个状态能从哪些地方转移过来,即按照正常的游戏顺序,下一轮该如何选择,按照规则我们知道,下一轮能选择的位置
必须满足如下条件:
1.
2.
设所有满足条件的k的数量为,则转移方程为
初始状态为,其中
为任意数,
为最大数的下标
最后的答案为
因为只需要保证,所以
和
的大小关系不确定,因此按照下标从大到小计算是不行的,可能会统计到还没有计算的状态。因此,为了保证能顺利转移,我们按照
数组从大到小排序,按照排序后原本下标的顺序进行计算,这样就可以保证统计到的都是已经计算过的状态了,转移的时候每次需要统计大于
的下标中,值大于
的所有状态的和,单次转移的复杂度为
,倒序枚举
,对同一个
,用一个
记录之前状态的和可以把单次转移复杂度将为
,这样的话总复杂度就为
。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int n,p[5010],inv[5010];
struct poss{
int id,v;
}a[5010];
int f[5010][5010],pos[5010];
bool cmp(poss a,poss b)
{
return a.v>b.v;
}
const int mod=998244353;
int fast_pow(int x,int a)
{
int ans=1;
for (;a;a>>=1,x=1ll*x*x%mod)
if (a&1) ans=1ll*ans*x%mod;
return ans;
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&p[i]);
a[i].id=i,a[i].v=p[i];
}
sort(a+1,a+1+n,cmp);
for (int i=0;i<=n;i++)
f[a[1].id][i]=1,f[i][n]=1,pos[a[i].v]=i;
for (int i=1;i<=n;i++)
inv[i]=fast_pow(i,mod-2);
for (int i=2;i<=n;i++)
{
int sum=0,num=0;
for (int j=n-1;j>=0;j--)
{
if (p[j+1]>a[i].v)
{
sum+=f[j+1][a[i].id],num++;
if (sum>=mod) sum-=mod;
}
f[a[i].id][j]=(1ll*sum*inv[num]+1)%mod;
}
}
int ans=0;
for (int i=1;i<=n;i++)
ans=(ans+f[i][0])%mod;
printf("%d\n",1ll*(ans)*inv[n]%mod);
}

京公网安备 11010502036488号