题目描述
所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:
43#9865#045
+ 8468#6633
44445509678
其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是5和3,第二行的数字是5。
现在,我们对问题做两个限制:
首先,我们只考虑加法的虫食算。这里的加法是N进制加法,算式中三个数都有N位,允许有前导的0。
其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。如果这个算式是NN进制的,我们就取英文字母表午的前N个大写字母来表示这个算式中的0到N-1这NN个不同的数字:但是这N个字母并不一定顺序地代表0到N−1。输入数据保证N个字母分别至少出现一次。
BADC
+CBDA
DCCC
上面的算式是一个4进制的算式。很显然,我们只要让ABCD分别代表0123,便可以让这个式子成立了。你的任务是,对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解。
输入输出格式
输入格式:
包含四行。
第一行有一个正整数N((N≤26)。
后面的三行,每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这3个字符串左右两端都没有空格,从高位到低位,并且恰好有N位。
输出格式:
一行,即唯一的那组解。
解是这样表示的:输出N个数字,分别表示A,B,C,…所代表的数字,相邻的两个数字用一个空格隔开,不能有多余的空格。
输入输出样例
输入样例#1
5
ABCED
BDACE
EBBAA
输出样例#1:
1 0 3 4 2
很容易想到dfs
一开始的想法:将ABCD…分别全部赋值 再进行计算
#include<stdio.h>
#include<string.h>
int N;
int used[28];
char left[28];
char right[28];
char sum[28];
int cast[30];
int ans[30];
int leap;
void search(int t)
{
int i,j;
if(leap==1)
{
return ;
}
else if(t==N)
{
if(cast[left[0]-'A']+cast[right[0]-'A']>=N) 最高位不能进位
return ;
memset(ans,0,sizeof(int)*N);
leap=1;
for(i=N-1,j=0;i>=0;i--,j++)
{
ans[j]+=cast[left[i]-'A']+cast[right[i]-'A'];
if(ans[j]>=N)
{
ans[j+1]=ans[j]/N;
ans[j]%=N;
}
if(ans[j]!=cast[sum[i]-'A'])
{
leap=0;
return ;
}
}
if(leap==1)
{
for(i=0;i<N;i++)
printf("%d ",cast[i]);
}
}
else
{
for(i=0;i<N;i++)
{
if(!used[i])
{
used[i]=1;
cast[t]=i;
search(t+1);
used[i]=0;
}
}
}
}
int main(void)
{
scanf("%d",&N);
scanf("%s%s%s",left,right,sum);
search(0);
}
发现时间复杂度太高并不能AC
后来琢磨了洛谷题解中大佬的代码和思路:dfs+剪枝
从右往左从上到下搜索 一个个位置进行赋值(即碰到E若E未赋值则搜索进行赋值 若E已赋值则继续搜索)
搜索的过程中进行剪枝提前去掉不成立的式子
#include<stdio.h>
#include<string.h>
int n;
char s[4][30]; 记录三个字符串
int used[30]; 记录数字是否使用
int cast[30]; 记录字母是否使用
int leap; 是否找到正确答案
int change(char c)
{
return c-'A'; 将字母转换为数字
}
void search(int x,int y,int t) x表示行,y表示列,t表示进位
{
int i;
if(leap==1) 如果已经有正确值则结束
return ;
if(y==-1) 如果能运行到-1列则说明式子正确
{
if(t==0) 最高位不能有进位
{
for(i=0;i<n;i++)
printf("%d ",cast[i]);
leap=1;
return ;
}
return ;
}
for(i=y;i>=0;i--) 减小时间复杂度重点:剪枝
{
int a1=cast[change(s[1][i])];
int a2=cast[change(s[2][i])];
int a3=cast[change(s[3][i])];
if(a1==-1||a2==-1||a3==-1)
continue;
if((a1+a2)%n!=a3&&(a1+a2+1)%n!=a3)
return ;
}
if(cast[change(s[x][y])]==-1) 如果该位置代表的字母未赋值
{
for(i=n-1;i>=0;i--) PS:从n-1到0会快一些
{
if(!used[i])
{
if(x!=3) 如果不是第三行 搜索+回溯
{
cast[change(s[x][y])]=i;
used[i]=1;
search(x+1,y,t); 搜索该列的下一行
cast[change(s[x][y])]=-1;
used[i]=0;
}
else
{
int sum=cast[change(s[1][y])]+cast[change(s[2][y])]+t;
if(sum%n!=i) 说明i不能满足上两个数相加
continue;
used[i]=1;
cast[change(s[x][y])]=i;
search(1,y-1,sum/n); 搜索前一列的第一行,同时修改进位
used[i]=0;
cast[change(s[x][y])]=-1;
}
}
}
}
else
{
if(x!=3)
{
search(x+1,y,t);
}
else
{
int sum=cast[change(s[1][y])]+cast[change(s[2][y])]+t;
if(sum%n!=cast[change(s[3][y])]) 判断这一列的式子是否正确 第二个剪枝
return ;
search(1,y-1,sum/n);
}
}
}
int main(void)
{
scanf("%d",&n);
scanf("%s%s%s",s[1],s[2],s[3]);
memset(cast,-1,sizeof(int)*n);
search(1,n-1,0); 从右往左从上到下 即从第一行的第n-1列开始搜索(即最后一列)
}