版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a54665sdgf/article/details/80722335
题目大意:36张牌分成9堆,每堆4张牌,你每次可以任取两堆顶部的牌,但这两张牌点数必须相同。求把所有的牌都拿走的概率。
思路:可以用dfs的方法模拟抽牌过程。我们定义状态为“每堆牌剩余数量的集合”,由于重复状态较多,而且总状态数较少(5^9),所以可以用dp的思想,记忆化搜索。
这道题的关键是每种状态下的dp值以及每堆牌的数量该如何保存。
最容易想到的是定义一个9维数组,每堆牌的张数用一个维度表示,但这种方法写起来太麻烦而且不美观。
另一种方法是用5进制编码来表示9堆牌的剩余数量,代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define FRER() freopen("i.txt","r",stdin)
using namespace std;
const int N=2000000;
double d[N];
char a[10][5];
int Pow(int x,int e)
{
int res=1;
while(e)
{
if(e&1)
res*=x;
x*=x;
e>>=1;
}
return res;
}
double dp(int x)
{
if(d[x]>=0)
return d[x];
int p,q,cnt=0;
double sum=0;
for(int i=0; i<9; ++i)
for(int j=i+1; j<9; ++j)
{
p=x%(Pow(5,i+1))/Pow(5,i);
q=x%(Pow(5,j+1))/Pow(5,j);
if(p&&q&&a[i][p]==a[j][q])
{
sum+=dp(x-Pow(5,i)-Pow(5,j));
cnt++;
}
}
return d[x]=cnt?sum/cnt:0;
}
int main()
{
//FRER();
while(1)
{
fill(d,d+N,-1);
d[0]=1;
for(int i=0; i<9; ++i)
for(int j=1; j<=4; ++j)
if(scanf(" %c%*c",&a[i][j])!=1)
return 0;
int x=0;
for(int i=0; i<9; ++i)
x+=4*Pow(5,i);
printf("%f\n",dp(x));
}
return 0;
}
但这种方法的缺点是需要不断地编码和解码,浪费了许多不必要的时间。一种改进的方法是用一个9元组c[9]来表示每堆牌剩余的数量,由于每次dp的时候只需要知道当前的状态,所以只需在每次dp前后修改一下这个9元组的值即可。为了简洁表示,仍然用编码的方式来保存dp值,但不需要频繁地编码和解码了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define FRER() freopen("i.txt","r",stdin)
using namespace std;
const int N=2000000;
double d[N];
char a[10][5];
int c[9];
double dp()
{
int x=0;
for(int i=0; i<9; ++i)
x=x*5+c[i];
if(d[x]>=0)
return d[x];
int cnt=0;
double sum=0;
for(int i=0; i<9; ++i)
for(int j=i+1; j<9; ++j)
{
if(c[i]&&c[j]&&a[i][c[i]]==a[j][c[j]])
{
c[i]--,c[j]--;
sum+=dp();
c[i]++,c[j]++;
cnt++;
}
}
return d[x]=cnt?sum/cnt:0;
}
int main()
{
//FRER();
while(1)
{
fill(d,d+N,-1);
fill(c,c+9,4);
d[0]=1;
for(int i=0; i<9; ++i)
for(int j=1; j<=4; ++j)
if(scanf(" %c%*c",&a[i][j])!=1)
return 0;
printf("%f\n",dp());
}
return 0;
}