本题让我学习了关于组合数的递归实现方法,此前做过一道全排列的也是用dfs实现,但没有想到组合也是类似的道理。数学中的排列与组合都可以利用dfs来完成应该要熟练掌握。此处附上我拜访别人博客的学习链接http://www.cnblogs.com/luxiaoxun/archive/2012/08/08/2628153.html
题目要求找出最少翻动几个棋子才能达到要求。首先前翻和后翻没有影响,例如先翻A再翻B跟先翻B再翻A一样。那么就是采用枚举的方法,所以我们可以选择0个,1个,····16个翻。
此时我们就需要用到组合数的知识,利用dfs实现,在这种组合下判断是否已经达到要求如果是那么就完成了应该退出,如果一直翻完16个还没有达到要求那么就是Impossible。
~要注意的地方就是如果翻了以后没有达到要求要记得翻回来不要破坏原来的数组。
#include<stdio.h> #include<string.h> char a[20]; int s[20],visit[20],ans[20]; int flag=0; void flip(int b[],int x) //翻转函数 { b[x]=!b[x]; if(x%4<3) b[x+1]=!b[x+1]; if(x%4>0) b[x-1]=!b[x-1]; if(x/4>0) b[x-4]=!b[x-4]; if(x/4<3) b[x+4]=!b[x+4]; } int is_win(int b[]) { int i; for(i=0;i<15;i++) { if(b[i]!=b[i+1]) return 0; } return 1; } void dfs(int depth,int start,int r) { int i; if(flag) return ;//若找到就返回~ if(depth==r) { for(i=0;i<r;i++) flip(s,ans[i]); if(is_win(s)) { flag=1; //表示已经找到 printf("%d\n",depth); } else //如果没找到要恢复成原来的数组 for(i=0;i<r;i++) flip(s,ans[i]); return ; } //下面这个写的跟链接中的不一样 但个人感觉循环末尾放在整个数组的最后也是可以的,只要起始位置一直在往前推进就行了,不知道对不对·或者会不会对效率有什么影响 for(i=start;i<16;i++) { visit[i]=1; //标记使用过 ans[depth]=i; //保存当前元素的序号 dfs(depth+1,i+1,r);//递归搜索 visit[i]=0; //恢复自由身 } } int main () { flag=0; memset(s,0,sizeof(s)); int i,j; for(i=0;i<16;i++) { scanf("%c",&a[i]); if(a[i]=='\n') i--; else if(a[i]=='b') s[i]=1; else if(a[i]=='w') s[i]=0; } if(is_win(s)) printf("0\n"); else for(i=1;i<=16;i++) { dfs(0,0,i); if(flag==1) break; } if(i==17) printf("Impossible\n"); return 0; }
今天做2965的时候发现是同类型的 用同样的方法做直接TLE,2965的一起翻转的地方比较多导致了超时,所以应该优化下,在保存当前个的时候就翻转,不要集中到最后翻转,这样会有很多重复的。。。详情见2965的那篇。。。
此处附上优化后的
#include<stdio.h> #include<string.h> char a[20]; int s[20],visit[20],ans[20]; int flag=0; void flip(int b[],int x) //翻转函数 { b[x]=!b[x]; if(x%4<3) b[x+1]=!b[x+1]; if(x%4>0) b[x-1]=!b[x-1]; if(x/4>0) b[x-4]=!b[x-4]; if(x/4<3) b[x+4]=!b[x+4]; } int is_win(int b[]) { int i; for(i=0;i<15;i++) { if(b[i]!=b[i+1]) return 0; } return 1; } void dfs(int depth,int start,int r) { int i; if(flag) return ;//若找到就返回~ if(depth==r) { if(is_win(s)) { flag=1; //表示已经找到 printf("%d\n",depth); } else //如果没找到要恢复成原来的数组 return ; } //下面这个写的跟链接中的不一样 但个人感觉循环末尾放在整个数组的最后也是可以的,只要起始位置一直在往前推进就行了,不知道对不对·或者会不会对效率有什么影响 for(i=start;i<16;i++) { if(!visit[i]) { visit[i]=1; //标记使用过 ans[depth]=i; //保存当前元素的序号 //下面一行对之前的代码进行了优化 flip(s,ans[depth]); dfs(depth+1,i+1,r);//递归搜索 flip(s,ans[depth]); visit[i]=0; //恢复自由身 } } } int main () { flag=0; memset(s,0,sizeof(s)); int i,j; for(i=0;i<16;i++) { scanf("%c",&a[i]); if(a[i]=='\n') i--; else if(a[i]=='b') s[i]=1; else if(a[i]=='w') s[i]=0; } if(is_win(s)) printf("0\n"); else for(i=1;i<=16;i++) { dfs(0,0,i); if(flag==1) break; } if(i==17) printf("Impossible\n"); return 0; }