博弈dp入门 POJ - 1678 HDU - 4597
本来博弈还没怎么搞懂,又和dp搞上了,哇,这真是冰火两重天,爽哉妙哉。
我自己的理解就是,博弈dp有点像对抗搜索的意思,但并不是对抗搜索,因为它是像博弈一样,大多数以当前的操作者来dp,光想是想不通的,上题练一练。
POJ - 1678 I Love this Game!
题目大意:有两个人正在玩游戏,在给出的一堆数中,玩家1先在[a,b]的范围中挑选出一个x1,然后玩家2再挑选有个y1满足a≤y1-x1≤b,然后玩家1再挑选一个x2满足a≤x2-y1≤b,以此轮流操作,直到某个玩家不能再进行操作,此时玩家1的分值为x1+x2+...,玩家2的分值为y1+y2+...,两个玩家都足够聪明,求最大的分差是多少?
其实不管就算是在博弈里,我觉得最难理解的也就是那句都足够聪明(我不够聪明怎么办),然后就在想如果足够聪明接下来会怎么做,然后推推推半天又不确定自己模拟的是不是就是都足够聪明下的情况,其实反过来想,既然都足够聪明,那么他们都已经意料到这场博弈的结果如何,所以说是当前状态博弈出最后状态,更不如说是最后状态反演出当前状态,已知结果时当前会做怎样的抉择(个人理解).
所以放在这题就是这么思考,首先因为0<a,而a≤x1≤b,x1<y1<x2<y2<..所以我们可以先把非正整数的数过滤掉,只留下正整数,然后再排序,这样就是在一个有序的的正整数序列中选择,那么我们就可以dp[i]就是当前选择了第i个数之后的最大分差,注意是当前,不是先手,也不是后手。那么当前(第i轮)选择完后,(第i+1轮)换下一个人就是下一个人是当前,那第i+1轮的人选择完后,他肯定保证自己是最大分差,所以回到第i轮,能取到的最大分差就是x[i]-max(第i+1的分差),有点绕,详情见代码
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int N=10118; 5 const int inf=0x3f3f3f3f; 6 int t,n,m,a,b,x[N],dp[N];//dp[i]为当前操作者取走第i个数的最大分差 7 int by(int p) 8 { 9 if(dp[p]!=-inf)//类似记忆化搜索 10 return dp[p]; 11 int ans=-inf; 12 for(int i=p+1;i<m&&x[i]-x[p]<=b;i++) 13 if(x[i]-x[p]>=a)//为什么取最大呢,因为是相等于当前取了p这个数,然后操作轮到下一个人, 14 ans=max(ans,by(i));//而下一个人足够聪明,那么他的分差应该是最大的 15 if(ans==-inf)//如果下一个人已经不能操作了,当前最大分差就是当前取的分值 16 return dp[p]=x[p]; 17 return dp[p]=x[p]-ans;//当前取的分值减去下一操作者能取得的最大分差就是当前最大分差 18 } 19 int main() 20 { 21 scanf("%d",&t); 22 while(t--) 23 { 24 scanf("%d%d%d",&n,&a,&b); 25 m=1; 26 for(int i=1;i<=n;i++) 27 { 28 dp[i]=-inf; 29 scanf("%d",&x[i]); 30 if(x[i]>0)//过滤掉非正整数 31 x[m++]=x[i]; 32 } 33 sort(x+1,x+m); 34 x[0]=0,dp[0]=-inf; 35 printf("%d\n",-by(0));//因为我多加了个0作为先手,返回的是0-ans,所以答案要取负 36 } 37 return 0; 38 }
HDU - 4597 Play Game
题目大意:Alice和Bob(这两博弈大佬)在玩一个游戏,有两堆牌,每次Alice和Bob只能从牌堆的两边取走一张牌,并得到相应的分数,问Alice先手最大能取得的分数是多少?
说是博弈dp,其实这题也属于区间dp,我们先用博弈dp解决,和第一题类似我们可以dp[i][j][k][l]表示第一堆还剩i~j,第二堆时还剩k~l时的当前操作者的最大分差,设分差为dis,alice得分a,bob得分b,所有牌取完的总分sum,有x-y=dis,x+y=sum,那么x=(sum+dis)/2,不过思路处理上和上一题有点出入,上一题是挑选了之后去由下一个人的最大分差来得到目前最大分差,这一题是当前有4种挑选结果,然后取最大的分差,详情见代码
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=28,inf=0x3f3f3f3f; 6 int t,n,a[N],b[N],dp[N][N][N][N];//dp[i][j][k][l]第一堆牌剩i~j,第二堆牌剩k~l时当前操作者最大分差 7 int dfs(int la,int ra,int lb,int rb) 8 { 9 if(la>ra&&lb>rb) 10 return 0; 11 if(dp[la][ra][lb][rb]!=inf) 12 return dp[la][ra][lb][rb]; 13 int ans=-inf,a1,a2,b1,b2; 14 if(la<=ra) 15 { 16 a1=a[la]-dfs(la+1,ra,lb,rb);//挑选a[la]后在下一个人的最大分差下能得到的最大分差 17 a2=a[ra]-dfs(la,ra-1,lb,rb);//挑选a[ra]后在下一个人的最大分差下能得到的最大分差 18 ans=max(ans,max(a1,a2)); 19 } 20 if(lb<=rb) 21 { 22 b1=b[lb]-dfs(la,ra,lb+1,rb);//挑选b[lb]后在下一个人的最大分差下能得到的最大分差 23 b2=b[rb]-dfs(la,ra,lb,rb-1);//挑选b[rb]后在下一个人的最大分差下能得到的最大分差 24 ans=max(ans,max(b1,b2)); 25 } 26 return dp[la][ra][lb][rb]=ans;//四种情况取最大即当前最大分差 27 } 28 int main() 29 { 30 scanf("%d",&t); 31 while(t--) 32 { 33 int sum=0; 34 scanf("%d",&n); 35 for(int i=1;i<=n;i++) 36 { 37 scanf("%d",&a[i]); 38 sum+=a[i]; 39 } 40 for(int i=1;i<=n;i++) 41 { 42 scanf("%d",&b[i]); 43 sum+=b[i]; 44 } 45 memset(dp,inf,sizeof(dp)); 46 printf("%d\n",(sum+dfs(1,n,1,n))/2); 47 } 48 return 0; 49 }
还有区间dp,就是dp[i][j][k][l]表示第一堆还剩i~j,第二堆时还剩k~l时,在剩余分的当前操作者能取到的最大分值。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=28; int t,n,a[N],b[N],dp[N][N][N][N];//dp[i][j][k][l]第一堆牌剩i~j,第二堆牌剩k~l时当前操作者最大分值 int dfs(int la,int ra,int lb,int rb,int sum) { if(la>ra&&lb>rb) return 0; if(dp[la][ra][lb][rb]!=0) return dp[la][ra][lb][rb]; int ans=0; if(la<=ra) { int a1=sum-dfs(la+1,ra,lb,rb,sum-a[la]);//剩余的分减去下一个操作者能得到的最大分 int a2=sum-dfs(la,ra-1,lb,rb,sum-a[ra]); ans=max(ans,max(a1,a2)); } if(lb<=rb) { int b1=sum-dfs(la,ra,lb+1,rb,sum-b[lb]); int b2=sum-dfs(la,ra,lb,rb-1,sum-b[rb]); ans=max(ans,max(b1,b2)); } return dp[la][ra][lb][rb]=ans; } int main() { scanf("%d",&t); while(t--) { int sum=0; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); sum+=a[i]; } for(int i=1;i<=n;i++) { scanf("%d",&b[i]); sum+=b[i]; } memset(dp,0,sizeof(dp)); printf("%d\n",dfs(1,n,1,n,sum)); } return 0; }