总时间限制: 1000ms 内存限制: 65536kB
描述:
所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:
43X98650X45
+ 8468X6633
-----------------
44445506978
其中”X”代表被虫子啃掉的数字。
根据算式,我们很容易判断:第一行的两个数字分别是5和3,第二行的数字是5。
现在,我们对问题做两个限制:
首先,我们只考虑加法的虫食算。这里的加法是N进制加法,算式中三个数都有N位,允许有前导的0。
其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。
如果这个算式是N进制的,我们就取英文字母表午的前N个大写字母来表示这个算式中的0到N-1这N个不同的数字:但是这N个字母并不一定顺序地代表0到N-1)。
输入数据保证N个字母分别至少出现一次。
BADC
+ CBDA
----------
DCCC
上面的算式是一个4进制的算式。很显然,我们只要让ABCD分别代表0123,便可以让这个式子成立了。
你的任务是,对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解。
输入
输入文件alpha.in包含4行。第一行有一个正整数N(N<=26),后面的3行每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这3个字符串左右两端都没有空格,从高位到低位,并且恰好有N位。
输出
输出文件alpha.out包含一行。在这一行中,应当包含唯一的那组解。解是这样表示的:输出N个数字,分别表示A,B,C……所代表的数字,相邻的两个数字用一个空格隔开,不能有多余的空格。
样例输入
5
ABCED
BDACE
EBBAA
样例输出
1 0 3 4 2
数据规模
对于30%的数据,保证有N≤10;
对于50%的数据,保证有N≤15;
对于全部的数据,保证有N≤26。
思路分析
因为我们搜索时需列举每一个字母对应的数字,所以每次的时间复杂度为n的阶乘,搜索次数过多一定会超时,所以此题必须大量剪枝。
其次,如果按正常搜索顺序,高位会受到进位的影响,搜索出许多错误答案,浪费时间,所以要预处理搜索字母的顺序。
知道这两点,便可编出正确代码,如下:
#include<cstdio>
#include<cstdlib>
#include<cstring>
int n,v[95];
char a[30],b[30],c[30],d[30]; //d数组用来存所有字母的先后搜索(从上到下,从左到右)
bool flag[95]; //flag数组存字母(init中)和 数字(cheak和dfs)是否使用过
int init()
{
int tot=0;
for(int i=n-1;i>=0;i--)
{
if(tot==n) break; // 如果 n个字母全部存入d数组,则不需要循环
if(flag[a[i]]==0) //如果a[i]没有存入d数组
{
d[++tot]=a[i]; // 将a[i]存入d数组
flag[a[i]]=1; //标记a[i]已存入d数组
}
if(flag[b[i]]==0) //如果b[i]没有存入d数组
{
d[++tot]=b[i]; // 将b[i]存入d数组
flag[b[i]]=1; //标记b[i]已存入d数组
}
if(flag[c[i]]==0) //如果c[i]没有存入d数组
{
d[++tot]=c[i]; // 将c[i]存入d数组
flag[c[i]]=1; //标记c[i]已存入d数组
}
}
memset(flag,0,sizeof(flag));
memset(v,-1,sizeof(v));
}
int ok()
{
int jw=0;
for(int i=n-1;i>=0;i--)
{
if((v[a[i]]+v[b[i]]+jw)%n!=v[c[i]])
return 0; //如果两个加数加进位不等于和,则返回0
jw=(v[a[i]]+v[b[i]]+jw)/n; //算出下一次进位
}
if(jw) //如果加完后还有进位,说明和达到n+1位,不合题意,返回0
return 0;
return 1; //不然说明结果合理,返回1
}
int cheak()
{
for(int i=n-1;i>=0;i--)
{
if(v[a[i]]>-1&&v[b[i]]>-1&&v[c[i]]>-1) // 三个数都知道
{
if((v[a[i]]+v[b[i]])%n!=v[c[i]]&&(v[a[i]]+v[b[i]]+1)%n!=v[c[i]]) //判断加法是否成立
return 0;
}
if(v[a[i]]>-1&&v[b[i]]>-1&&v[c[i]]==-1) //只知道两个加数
{
int c1,c2;
c1=(v[a[i]]+v[b[i]])%n; //求出和的两种情况
c2=(v[a[i]]+v[b[i]]+1)%n;
if(flag[c1]==1&&flag[c2]==1) return 0; //如果两种情况都被用过,说明c没有可取的值,返回0,以下同理
}
if(v[a[i]]>-1&&v[b[i]]==-1&&v[c[i]]>-1) //只知道一个加数a与和
{
int b1,b2;
b1=(v[c[i]]-v[a[i]]+n)%n;
b2=(v[c[i]]-v[a[i]]+n-1)%n;
if(flag[b1]==1&&flag[b2]==1) return 0;
}
if(v[a[i]]==-1&&v[b[i]]>-1&&v[c[i]]>-1) //只知道一个加数b与和
{
int a1,a2;
a1=(v[c[i]]-v[b[i]]+n)%n;
a2=(v[c[i]]-v[b[i]]+n-1)%n;
if(flag[a1]==1&&flag[a2]==1) return 0;
}
}
return 1;
}
void dfs(int x)
{
if(x>n) //如果x>n,说明搜索出一种情况
{
if(ok()==1) //判断是否合理
{
for(int i=65;i<n+64;i++) //输出
printf("%d ",v[i]);
printf("%d\n",v[n+64]);
exit(0); //因为题目说明只有一组解,所以直接结束搜索函数
}
return; //如果不满足条件则继续搜索
}
else
{
for(int i=n-1;i>=0;i--)
{
if(flag[i]==0) //如果i没有用过
{
flag[i]=1;
v[d[x]]=i; //将v[d[x]]置为i(也就是字母d[x]对应的数字)
if(cheak()==0) //剪枝,不满足则回溯
{
flag[i]=0;
v[d[x]]=-1;
continue;
}
dfs(x+1); // 搜索下一个字母
flag[i]=0; //回溯
v[d[x]]=-1;
}
}
}
}
int main()
{
scanf("%d\n",&n);
gets(a); //输入加数a,b
gets(b);
gets(c); //输入和c
init(); //预处理
dfs(1); //搜索
return 0;
}