原题:
思路:
我太弱了,竟然打了一个上午。。。。。。
一开始我竟然想从A到B枚举每一种排列最后检查。。。很显然这种方法效率特别低——O(N!)
于是想到了由低位到高位,由两个加数上的位到和上的位,逐个枚举数字。当然如果不剪枝的话就和第一种方法没什么区别
剪枝:
剪枝一:
这个剪枝比较好想,在每枚举完一列时(或者该列上的数字已被枚举了),检查上两行(加数位)之和加上上一位的进位对进制数取模是否等于最后一行(两数之和的位),如果不相等就说明当前状态无法得出正解,可以剪枝。
剪枝二:
这个有点难想到。
众所周知,加法中进位最多为1(小学数学知识)
那么对于每一位,如果该位上的三个数都被枚举过了,记第一行数为a,第二行为b,第三行为c,则可以分以下两种情况讨论:
- 上一位无进位,则a+b==c
- 上一位有进位,则a+b+1==c
如果对于以上两种情况均不成立,则说明当前状态无法得出正解,可以剪枝。
(如果有不理解的可以问我或者结合代码注释理解)
上代码:
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> using namespace std; int n,ans[30],use[30]={0}; char s[4][30]; //给每一个编号 int id(char ch){ return ch-'A'+1; } //输出结果 void print(){ for(int i=1;i<=n;i++) printf("%d ",ans[i]); } void dfs(int x,int y,int z){ if(x==0&&z==0){ print(); exit(0);//找到答案直接滚粗 } else{ //剪枝二 /*那么对于每一位,如果该位上的三个数都被枚举过了,记第一行数为a,第二行为b,第三行为c,则可以分以下两种情况讨论: 上一位无进位,则a+b==c 上一位有进位,则a+b+1==c*/ int a,b,c; for(int i=x-1;i>0;i--){ a=ans[id(s[1][i])];b=ans[id(s[2][i])];c=ans[id(s[3][i])]; if(a!=-1&&b!=-1&&c!=-1&&(a+b)%n!=c&&(a+b+1)%n!=c) return; } if(ans[id(s[y][x])]==-1){ //前两行 if(y<3){ for(int i=0;i<n;i++){ if(!use[i]){ ans[id(s[y][x])]=i;use[i]=1; dfs(x,y+1,z); ans[id(s[y][x])]=-1;use[i]=0; } } } //第三行 if(y==3){ for(int i=0;i<n;i++){ if(!use[i]){ int zz=ans[id(s[1][x])]+ans[id(s[2][x])]+z; //剪枝一 if(zz%n==i){ ans[id(s[y][x])]=i;use[i]=1; dfs(x-1,1,zz/n); ans[id(s[y][x])]=-1;use[i]=0; } } } } } else{ //前两行 if(y<3){ dfs(x,y+1,z); } //第三行 if(y==3){ int zz=ans[id(s[1][x])]+ans[id(s[2][x])]+z; //剪枝一 if(zz%n==ans[id(s[3][x])]){ dfs(x-1,1,zz/n); } } } } } int main(){ memset(ans,-1,sizeof(ans));//初始化为-1,因为每一个数也可以是0 scanf("%d",&n); scanf("%s%s%s",s[1]+1,s[2]+1,s[3]+1); dfs(n,1,0);//从最低位的第一个加数开始枚举 return 0; }
施工Van♂毕!ヾ(゚∀゚ゞ)~~~~~~~