原题链接:https://ac.nowcoder.com/acm/contest/73760/F
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
小红这天又和小紫发起了一场对战。对战的规则如下:
两人各有一些怪兽卡。每回合两人随机的从自己当前存活的怪兽卡中抽取一张发起决斗,战斗力低的怪兽卡死亡。如果两张怪兽卡战斗力相同,则无事发生。
游戏会进行到“如果抽卡,则 100% 的概率无事发生”或者“有一方卡牌被用完”时结束。请你计算小红和小紫游戏进行回合数的期望。
输入描述:
第一行输入两个正整数n,m,分别代表小红和小紫的怪兽卡数量。 第二行输入n个正整数ai,代表小红的每张怪兽卡战斗力。 第三行输入m个正整数bi,代表小紫的每张怪兽卡战斗力。 1≤n,m≤50 1≤ai,bi≤2
输出描述:
一个整数,代表最终回合数期望对1e9+7取模的值。可以证明,最终的答案一定是个有理数,你只需要输出其对1e9+7取模的结果。 分数取模的定义:假设答案是x/y,那么其对p取模的答案是找到一个整数a满足a∈[0,p−1]且a∗y对p取模等于x。
示例1
输入
1 1 1 1
输出
0
说明
显然第一回合都不需要进行,因为一定会无事发生。
示例2
输入
2 3 2 2 1 1 1
输出
3
说明
一定会进行 3 回合,直到小紫的怪兽全部死亡。
示例3
输入
2 1 1 2 2
输出
2
说明
有 1/2 的概率会进行 1 回合,1/4 的概率会进行 2 回合,1/8 的概率会进行 3 回合…… 最终答案是 1/2*1+1/4*2+1/8*3+…… 该无穷级数收敛于 2。
解题思路:
经典的期望dp,赛时由于状态转移方程出了一点小差错导致没写出来。题目说了只有俩种战斗力的牌,分别为1和2,2已经是最大的牌了,所以2肯定不会死亡,也就是说小红小紫2的牌的数量肯定是不会变的,所以我们只需要考虑俩人1的牌的数量即可,所以dp状态设计时只需要将俩人1的牌的数量设计进入状态即可,我们还需要特判一种情况,就是当俩人都没有2号牌时,那么期望回合数就是0,直接输出0即可,然后考虑dp处理即可。
期望dp处理过程:
状态定义:
定义f[i][j]表示小红战斗力为1的牌的数量为i,小紫战斗力为1的牌的数量为j时的期望回合数。
初始化:
f[0][0]=0,当俩人都没有战斗力为1的卡牌时,期望回合数为0
状态转移:
- 当小红抽中1,小紫抽中1,表示无事发生,转移到f[i][j]
- 当小红抽中2,小紫抽中2,表示无事发生,转移到f[i][j]
- 当小红抽中1,小紫抽中2,小红1的卡牌数减掉1,转移到f[i-1][j]
- 当小红抽中2,小紫抽中1,小紫1的卡牌数减掉1,转移到f[i][j-1]
1,2俩种无事发生的概率为p1,第三种情况概率为p2,第四种情况概率为p3
p2=i*inv(i+t[2]) * tt[2]*inv(j+tt[2])
p3=t[2]*inv(i+t[2]) * j*inv(j+tt[2])
最后加上的这个1表示要把当前这个回合加上,不管从哪种情况转移过来,当前回合肯定加上
f[i][j]=p1*f[i][j]+p2*f[i-1][j]+p3*f[i]][j-1]+1 p1+p2+p3=1
(1-p1)*f[i][j]=p2*f[i-1][j]+p3*f[i][j-1]+1 1-p1=p2+p3
(p2+p3)*f[i][j]=p2*f[i-1][j]+p3*f[i][j-1]+1
f[i][j]=(p2*f[i-1][j]+p3*f[i][j-1]+1)*inv(p2+p3)
最终答案:
答案为小红有t[1]张战斗力为1的卡牌,小紫有tt[1]张战斗力为1的卡牌时的期望回合数
最终答案就是f[t[1]][tt[1]],t[1]表示小红战斗力为1的卡牌数,tt[1]表示小紫战斗力为1的卡牌数。
时间复杂度:状态数量为O(n*m),转移为O(1),所以时间复杂度为O(n*m),n=50,m=50,这个时间复杂度很低肯定是可以过的。
空间复杂度:dp数组f为二维,所以空间复杂度为O(n*m),所以空间要求非常低,题目给了俩百多M空间,空间是肯定足够的。
cpp代码如下:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N=55,mod=1e9+7;
int n,m;
int t[N],tt[N];
LL f[N][N];
LL qmi(LL x,LL k) //利用费马小定理,快速幂求逆元
{
LL res=1;
while(k){
if(k&1)res=res*x%mod;
x=x*x%mod;
k>>=1;
}
return res;
}
LL inv(LL x) //求逆元
{
return qmi(x,mod-2);
}
int main()
{
cin>>n>>m;
int x;
for(int i=0;i<n;i++)
{
cin>>x;
t[x]++; //t[1]记录小红卡牌中1的个数,t[2]记录2的个数
}
for(int i=0;i<m;i++)
{
cin>>x;
tt[x]++; //tt[1]记录小紫卡牌中1的个数,tt[2]记录2的个数
}
//小红和小紫都没有战斗力为2的卡牌,期望回合数为0
if(!t[2] && !tt[2]){
cout<<0<<endl;
return 0;
}
//初始化,小红和小紫都没有战斗力为1的卡牌数
f[0][0]=0;
//dp
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
{
//p2表示小红抽中1小紫抽中2的概率
//p3表示小红抽中2小紫抽中1的概率
LL p2=i*inv(i+t[2])%mod*tt[2]%mod*inv(j+tt[2])%mod;
LL p3=t[2]*inv(i+t[2])%mod*j%mod*inv(j+tt[2])%mod;
f[i][j]=(p2*f[i-1][j]%mod+p3*f[i][j-1]%mod+1)%mod*inv(p2+p3)%mod;
}
//答案为小红有t[1]张战斗力为1的卡牌,小紫有tt[1]张战斗力为1的卡牌时的期望回合数
cout<<f[t[1]][tt[1]]<<endl;
return 0;
}