百度之星2013年4月27日竞赛题目一 Fir
百度之星2个小时只能做1题已经成定式了....只求1题赚点小分就知足了..orz
比赛一开始,看到第一题激动了半天,正好这几天在学概率dp,看来RP守恒真不是盖得= =,飞速敲完后,完美过了样例,正自恋着准备交,听到X神说被坑了...仔细一想,当时的想法确实是错误的。幸好还没交!
错误代码1:
思路:ans[i]表示长度为i的串的期望时间,则ans[i]=∑(max(ans[j-1],ans[i-j])+1)*(1/i) [j代表当前爆炸的位置],然后从前往后递推就行了
#include<stdio.h> double dp[410]; double max(double x,double y){ return x>y?x:y; } void init(){ int i,pos; dp[0]=0;dp[1]=1; for(i=2;i<=400;i++){ double temp=1.0/i; for(pos=1;pos<=i;pos++){ dp[i]+=(max(dp[pos-1],dp[i-pos])+1)*temp; } } } int main() { int T,n; init(); scanf("%d",&T); while(T--){ scanf("%d",&n); printf("%lf\n",dp[n]); } return 0; }
从上面可以看到,对于一个长度为n的串,分成a,b两段时,我们直接用a和b的期望来算的,实际上a,b之间再爆炸的时候是相互影响的,也就是不能分别独立出来算然后取最大值。
想到这点之后出现了错误代码2:
思路:dp[i][j]表示炸开以后的两段分别长i,j时,剩下时间的期望,则:
dp[i][j]=∑(max(dp[k1-1][i-k1],dp[k2-1][j-k2])+1)*p [k1,k2分别为2段炸开的位置]
然后长为n的期望时间就是dp[n][0]
#include<stdio.h> #include<string.h> double dp[410][410]; //两段分别为a,b时的期望 double max(double x,double y){ return x>y?x:y; } void init() { int i,j,pos1,pos2; memset(dp,0,sizeof(dp)); dp[0][0]=0; dp[0][1]=dp[1][0]=1,dp[1][1]=1; for(i=2;i<=400;i++){ for(j=1;j<=i;j++){ double temp=1.0/i/j; for(pos1=1;pos1<=i;pos1++) for(pos2=1;pos2<=j;pos2++) dp[i][j]+=(max(dp[pos1-1][i-pos1],dp[pos2-1][j-pos2])+1)*temp; dp[j][i]=dp[i][j]; } double temp=1.0/i; for(j=1;j<=i;j++) dp[i][0]+=(dp[j-1][i-j]+1)*temp; dp[0][i]=dp[i][0]; } } int main() { int T,n; init(); scanf("%d",&T); while(T--){ scanf("%d",&n); printf("%.5lf\n",dp[n][0]); } return 0; }
代码2的改进是,考虑到了分成的a,b两个爆炸不是独立的,对每种情况暴力枚举,复杂度大概是O(n^4),出来以后完美过了样例,但是保险起见还是没敢交。
后来听到Y神的想法觉得挺有道理,直接算概率确实很保险,因为上面第二种方法等于还是要用期望,而期望这个东西直接取最大值就会觉得怪怪的,直觉就是这样是错的。
于是就诞生了第三种代码,正误未知,但是思路肯定不会错,因为算概率的话,每种状态的转移都是有理可依的,复杂度O(n^4),只能打表用:
思路:dp[i][j]表示长度为i的串,j次爆炸完的概率,则:
如果dp[k-1][j-1]存在
dp[i][j]+=∑dp[k-1][j-1]*dp[i-k][h]*(1.0/i) (h小于等于i-k,小于等于j-1)
如果dp[i-k][j-1]存在
dp[i][j]+=∑dp[k-1][h]*dp[i-k][j-1]*(1.0/i) (h小于等于k-1,小于等于j-1)
这两种都存在,则还要减去重复算的一种情况.
#include<stdio.h> #include<string.h> double dp[410][410];//长度m用n次概率 void init() { int i,j,k,pos,lenth1,lenth2; memset(dp,0,sizeof(dp)); dp[0][0]=1; dp[1][0]=dp[0][1]=0; dp[1][1]=1; for(i=2;i<=400;i++){ double temp=1.0/i; for(j=1;j<=i;j++){ //分的次数 for(pos=1;pos<=i;pos++){ //爆炸的位置 lenth1=pos-1; //前一段长度 lenth2=i-pos; //后一段长度 for(k=0;k<=j-1&&k<=lenth1;k++) dp[i][j]+=dp[lenth2][j-1]*dp[lenth1][k]*temp; for(k=0;k<=j-1&&k<=lenth2;k++) dp[i][j]+=dp[lenth1][j-1]*dp[lenth2][k]*temp; if(dp[lenth1][j-1]&&dp[lenth2][j-1]) //减掉重复,这一步容易忘!找了好久 dp[i][j]-=dp[lenth1][j-1]*dp[lenth2][j-1]*temp; } } } } int main() { int T,n; freopen("input.txt", "rt", stdin); freopen("output.txt", "wt", stdout); init(); /*scanf("%d",&T); while(T--) { scanf("%d",&n); double ans=0; for(int i=1;i<=n;i++) ans+=dp[n][i]*i; printf("%.5lf\n",ans); }*/ for(int i=1;i<=400;i++) { double ans=0; for(int j=1;j<=i;j++) ans+=dp[i][j]*j; printf("%.5lf,",ans); } }
这个代码调好以后已经只剩5分钟了...确定n=10和n=400的正确性后...不管三七二十一直接交了..求RP爆发吧
总体来说还是我太弱了,概率dp这个东西,没有确定的递推式还是不要胡乱假设的好~