中山大学校队内部选拔赛试题2.5【End this painful contest with a simple problem】---------2015年2月9日
一:题意描述
某个寝室有两个比较懒的同学,他们每天都叫外卖来维持生活。考虑到都不想下楼,他们便想出了一个比较好的方法,规则如下:
每个人都有m张不同的牌,每张牌的面值都不同。现在进行n回合的游戏:每一次随机取出一张牌,并把该张牌的面值记录下来,
然后把牌放回,如此重复n次,最后总值较小的人输,下楼取外卖。
输入:m(<=100)(表示每个同学所拥有牌的个数),输入数组a[m](表示各张牌的值)(<=50),输入n(重复次数)(<=50)。
二:问题分析
本题数据的大小如上所示:我们通过分析可知,每个人的状态最多有2500种(50*50),那么我们如果直接根据两个人的状态直接模拟的话时间复杂度将达到
2500*2500。那么将不能承受。现在我们可以换一种方式考虑。要求其中一个人赢的概率我们需要分别求得第一个人所有可能取值的情况,第二个人所有可能
取值的情况。现在我们首先定义几个数组:
数组A[51][2501]:A[i][j]表示第一个人第i次拿牌且总值为j时的概率;
数组B[51][2501]:B[i][j]表示第二个人第i次拿牌且总值为j时的概率。
以上初始化条件是所有元素全部置为0,其中A[0][0]=B[0][0]=1.0。
数组sb[2501]:sb[i]表示第二个人经过n次拿牌后总值不超过i的概率。
以上全部元素置为0.
下面我们就第一个人的状态进行分析::对于第i次拿牌,我们可以从第i-1次拿牌转移而得。并且对于每一次拿牌,拿到每一张牌a[i]的概率为1/m。
那么我们可以得到状态转移公式:
A[i][j+a[k]]+=A[i-1][j]/m(其中k有m个取值)
同样的处理我们可以得到第二个人的状态转移方程:B[i][j+b[k]]+=B[i-1][j]/m。
现在我们研究如何求得sb[i]?B经过n次游戏后总值不超过的i的概率=不超过i-1的概率+B经过n次游戏总值等于i-1的概率。
通过定义可知递推公式为:sb[i]=sb[i-1]+B[n][i-1]。
整个算法的时间复杂度是O(M*N*N)。
三: AC代码
#include<iostream> #include<cstdio> #include<string.h> using namespace std; int m,n,a[100],b[100],maxa,maxb; double A[51][2501],B[51][2501]; double sb[2501]; int main() { int T; cin>>T; while(T--) { cin>>m; maxa=maxb=0; for(int i=0;i<m;i++) { cin>>a[i]; if(a[i]>maxa) maxa=a[i]; } for(int i=0;i<m;i++) { cin>>b[i]; if(b[i]>maxb) maxb= b[i]; } cin>>n; memset(A,0,sizeof(A)); memset(B,0,sizeof(B)); memset(sb,0,sizeof(sb)); A[0][0]=B[0][0]=1.0; for(int i=1;i<=n;i++) { for(int j=0;j<=maxa*(i-1);j++) { for(int k=0;k<m;k++) A[i][j+a[k]]+=A[i-1][j]/m; } } for(int i=1;i<=n;i++) { for(int j=0;j<=maxb*(i-1);j++) { for(int k=0;k<m;k++) B[i][j+b[k]]+=B[i-1][j]/m; } } sb[0]=0; double ans=0.0; for(int i=1;i<=50*n;i++) sb[i]=sb[i-1]+B[n][i-1]; for(int i=0;i<=maxa*n;i++) ans+=A[n][i]*sb[i]; printf("%.6lf\n",ans); } return 0; }
四:总结
在做动态规划的题目时要注意边界问题的处理技巧否则经常出错。