这个算是脱了很久很久的一片解题报告了。主要是运用动态规划求解期望问题的解法,一般用记忆化搜的写法更好理解。以几个题为例:
UVALive 5811,Card。
题意很简单:一副扑克牌共有13张黑桃,红心,梅花,方片和大小王各一张,input会给出黑桃,红心,梅花,方片的数量。问从随机分布的一堆牌中依次抽取一张牌,达到题中所要求的数量,至少需要抽取牌的期望,如果抽到大小王,则可以把它们当成任意花色,因为要求抽取的牌数最少,所以当然是当成最需要的了。
比赛训练时想了很久,都没有想出来,最后还是看了大牛的代码才略懂一二。主要是用dp值表示期望,记忆化搜直接出解,dp[a][b][c][d][big][small]表示抽取了a张黑桃,b张红心,c张梅花,d张方片,大小王分别当成了big和small(为4表示还没有用过大小王,0表示黑桃,1表示红心,2表示梅花,3表示方片),其他的看代码就知道了
View Code
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 #define see(x) cout<<#x<<":"<<x<<endl; 7 using namespace std; 8 const int maxn = 16; 9 double dp[maxn][maxn][maxn][maxn][5][5]; 10 bool vis[maxn][maxn][maxn][maxn][5][5]; 11 double aa, bb, cc, dd; 12 double dfs(int a, int b, int c, int d, int big, int small){ 13 if(vis[a][b][c][d][big][small]) return dp[a][b][c][d][big][small]; 14 vis[a][b][c][d][big][small] = 1; 15 int now[] = {a,b,c,d}; 16 if(big<4) now[big]++; 17 if(small<4) now[small]++; 18 double sum = now[0]+now[1]+now[2]+now[3]; 19 if(now[0]>=aa&&now[1]>=bb&&now[2]>=cc&&now[3]>=dd) return dp[a][b][c][d][big][small] = sum; 20 double tmp = 0.0; 21 if(a<13) tmp += dfs(a+1,b,c,d,big,small)*(13.0-a)/(54.0-sum); 22 if(b<13) tmp += dfs(a,b+1,c,d,big,small)*(13.0-b)/(54.0-sum); 23 if(c<13) tmp += dfs(a,b,c+1,d,big,small)*(13.0-c)/(54.0-sum); 24 if(d<13) tmp += dfs(a,b,c,d+1,big,small)*(13.0-d)/(54.0-sum); 25 if(big==4){ 26 double tmp1 = 1e10; 27 tmp1 = min(tmp1,dfs(a,b,c,d,0,small)); 28 tmp1 = min(tmp1,dfs(a,b,c,d,1,small)); 29 tmp1 = min(tmp1,dfs(a,b,c,d,2,small)); 30 tmp1 = min(tmp1,dfs(a,b,c,d,3,small)); 31 tmp += tmp1/(54.0-sum); 32 } 33 if(small==4){ 34 double tmp1 = 1e10; 35 tmp1 = min(tmp1,dfs(a,b,c,d,big,0)); 36 tmp1 = min(tmp1,dfs(a,b,c,d,big,1)); 37 tmp1 = min(tmp1,dfs(a,b,c,d,big,2)); 38 tmp1 = min(tmp1,dfs(a,b,c,d,big,3)); 39 tmp += tmp1/(54.0-sum); 40 } 41 if(tmp<1e-8) tmp = 1e10; //tmp初始值是0,然后累加求解,但是如果什么都没有加过的化,期望不是0,而是无穷大。坑!!! 42 return dp[a][b][c][d][big][small] = tmp; 43 } 44 45 int main(){ 46 int T, cas, i, j, k; 47 double ans; 48 scanf("%d",&T); 49 for(cas=1;cas<=T;cas++){ 50 scanf("%lf%lf%lf%lf",&aa,&bb,&cc,&dd); 51 if( max(0.0,aa-13)+max(0.0,bb-13)+max(0.0,cc-13)+max(0.0,dd-13)>2 ) ans = -1.0; 52 else{ 53 memset(vis,0,sizeof(vis)); 54 ans = dfs(0,0,0,0,4,4); 55 } 56 printf("Case %d: %.3lf\n",cas,ans); 57 } 58 return 0; 59 }
还有一道最近zoj月赛的题,zoj3640 Help Me Escape,里面也要用到记忆化搜(lz总是手贱,忘记写成记忆化的形势,贡献一个wa)
题意:有n个城市,每个城市有值ci,有个要从城市逃离的人,他有一个能量值f,当他到某个城市时,只要他的能量值f大于这个城市的ci值,他就可以逃离,否则他的能量值将增加ci,同时过一天后随机的被送往任一城市(也有可能还是这个城市),问这个人逃离这些城市的期望。
比赛的时候写了一个裸的求期望,果断t了~赛后用了一下记忆化搜就过了,跟上题类似,dfs时也是从初始的状态开始递推,dp[deep][f]表示以第deep天,此人能量值为f,之后逃脱需要多少天。最后在仔细考虑一下deep和f潜在的最大值,就很好求解了~
View Code
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 const int maxn = 105; 8 const int inf = (1<<30); 9 const double gold = (1+sqrt(5.0))/2; 10 int c[maxn], n, d[maxn]; 11 double dp[maxn][10005]; 12 double ans; 13 int cal(int c){ 14 return int(gold*c*c); 15 } 16 double dfs(int deep, int f){ 17 int i, j; 18 if(dp[deep][f]>-0.5){ 19 return dp[deep][f]; 20 } 21 double tmp = 0.0; 22 for(i=1;i<=n;i++){ 23 if(f>c[i]){ 24 tmp += (deep+(double)d[i])/n; 25 } 26 else{ 27 tmp += dfs(deep+1,min(f+c[i],10001))/n; 28 } 29 } 30 return dp[deep][f] = tmp; 31 } 32 int main(){ 33 int f, i, j, k, l; 34 while(~scanf("%d%d",&n,&f)){ 35 for(i=1;i<=n;i++){ 36 scanf("%d",&c[i]); 37 d[i] = cal(c[i]); 38 } 39 for(i=0;i<maxn;i++){ 40 for(j=0;j<10005;j++){ 41 dp[i][j] = -1.0; 42 } 43 } 44 c[0] = 0; 45 printf("%.3lf\n",dfs(0,f)); 46 } 47 return 0; 48 }
还有一些用记忆化搜不好求解的题目,也就是直接求解就好了的题~~,比如 URAL - 1776 Anniversary Firework,UVALive - 5721 Activation(这个求递推公式很奇葩,算做数学题一样~)