P1409题解
P1409 题解
博客里食用更佳。
题意
题目的意思浅显易懂:
\(n\) 个人站成一排,小明(临时演员)是第 \(m\) 个人,每轮上帝会投一次筛子,分为以下情况:
- 若掷到 \(1\),则队首的人获胜;
- 若掷到 \(2\),\(4\),\(6\),则队首的人出队;
- 若掷到 \(3\),\(5\),则队首的人排到队尾。
问:小明有多少的胜率?(注意:输出保留九位小数)
思路
这不就是一道 dp 和数学结合起来的题吗???
分析
我们就设二维数组 \(f[1005][1005]\),\(f[i][j]\) 表示有 \(i\) 个人,小明在第 \(j\) 个位置,小明的胜率。
易知,\(f[1][1]=1\),因为只有一个人的时候小明直接胜利!且由题意得 \(j\leqslant i\)。
接下来,我们需要把题目带进来,求出状态转移方程式(动态规划的递推式)。
- 掷到 \(1\),则队首的人获胜,这时候,小明有 \(\frac{1}{6}\) 的概率会输掉(对手的人胜利了,小明就输了)。
- 掷到 \(2\),\(4\),\(6\),则队首的人出队,此时,队首的人有 \(\frac{1}{2}\) 的概率会转回队尾,(那又是一个天道好轮回了……)也就是相当于小明又往前走了一步(\(j-1\)),而总人数(\(i\))不变,表示为 \(f[i][j-1]\)。
- 掷到 \(3\),\(5\),则队首的人排到队尾,此时,队首的人有 \(\frac{1}{3}\) 的概率出队,也就是相当于小明又往前走了一步(\(j-1\)),而总人数却又少了一个人(\(i-1\)),表示为 \(f[i-1][j-1]\)。
列出了上表,dp递推式也就很简单了,由于小明为主角,绝不能输,所以就不讨论第一种情况,所以,对于 \(x\),\(y\)(\(x\leqslant y\),\(x\),\(y\) 为正整数),都有 \(f[x][y]\),此时有 \(\frac{1}{2}\) 的概率跳到 \(f[x][y-1]\);有 \(\frac{1}{3}\) 的概率跳到 \(f[x-1][y-1]\) 所以推出动态规划递推式:
\(f[x][y]=\frac{1}{2}f[x][y-1]+\frac{1}{3}f[x-1][y-1]\)。
最后小明就会跳到 \(f[u][1]\)(\(u\) 为正整数且 \(u\leqslant n\))(这里 \(u\) 是代表当小明为第一个人时,总共还剩几人),小明就到了第一个,上帝要为他摇骰子了!而当小明为第一个时,\(j=1\),所以没有 \(f[u][0]\) 或 \(f[u-1][0]\),所以我们要单独推当 \(j=1\) 时的情况:他有 \(\frac{1}{6}\) 的概率会胜利,而又会有 \(\frac{1}{3}\) 的概率出局(要求小明的胜率,这种情况不讨论),也有 \(\frac{1}{2}\) 跳到队尾,也就是跳到 \(f[u][u]\)(又是一个天道好轮回),所以 \(f[u][1]=\frac{1}{6}+\frac{1}{2} f[u][u]\)。
这就可以推广到所有 \(j=1\) 时的情况,因此我们就会建立这么多个循环往复的等式,这是一个连续的多元一次方程组,是容易解决的,观察一下系数,然后就可以用代入消元法解决了。(谢谢 Aw顿顿 )
观察系数的代码如下:
for(int j=2; j<=i; j++){//观察系数
sum=sum/2;
num=num/2+f[i-1][j-1]/3;
}
总结:
- 坑点(也不算吧):输出保留九位小数。
- 需要的基础:dp+数学(数论)。
- dp 比较别致一点,因为 dp 数列可能会构成一个环,所以须要观察系数,然后就可以用代入消元法解决了。
- 当小明第一个人时,若遇到第二种情况,还需要重新排到队尾,千万不可结束,不然听取 WA 声一片。
废话不多说了,上总代码!
注释在代码里了
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
double f[1005][1005];
int main(){
cin>>n>>m;
f[1][1]=1;//初始化!只有一个人的时候你直接胜利!别忘了写这一行
for(int i=2; i<=n; i++){
double sum=0.5,num=1.0/6.0;
//1.0/6.0和1/6可有大区别了!
//1/6=0.166666667……,取整一下为0
//1.0/6.0则是相当于(double)1/6
for(int j=2; j<=i; j++){//观察系数
sum=sum/2;
num=num/2+f[i-1][j-1]/3;
}
//不断将f[i][j-1]带入f[i][j],最终就可以求出下面二式
f[i][i]=num/(1-sum);//解出了f[i][i]
f[i][1]=f[i][i]/2+1.0/6.0;//解出了f[i][1]
for(int j=2; j<i; j++) f[i][j]=f[i][j-1]/2+f[i-1][j-1]/3;//前面推出的解析式(递推式?到底说算是什么啊……)
}
printf("%.9lf",f[n][m]);//保留九位输出
return 0;
}
//P上水印

浙公网安备 33010602011771号