noip2004 虫食算 (深搜,倒序枚举+高斯消元解方程组)

来源:互联网 发布:淘宝原图制作软件 编辑:程序博客网 时间:2024/06/06 16:28
P1099虫食算
Accepted
标签:搜索 搜索与剪枝NOIP提高组2004

描述

所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:
43#9865#045
+ 8468#6633
= 44445506678
其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是5和3,第二行的数字是5。

现在,我们对问题做两个限制:
首先,我们只考虑加法的虫食算。这里的加法是N进制加法,算式中三个数都有N位,允许有前导的0。

其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。如果这个算式是N进制的,我们就取英文字母表午的前N个大写字母来表示这个算式中的0到N-1这N个不同的数字:但是这N个字母并不一定顺序地代表0到N-1)。输入数据保证N个字母分别至少出现一次。

BADC
+ CRDA
= DCCC
上面的算式是一个4进制的算式。很显然,我们只要让ABCD分别代表0123,便可以让这个式子成立了。你的任务是,对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解

格式

输入格式

输入包含4行。第一行有一个正整数N(N<=26),后面的3行每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这3个字符串左右两端都没有空格,从高位到低位,并且恰好有N位。

输出格式

输出包含一行。在这一行中,应当包含唯一的那组解。解是这样表示的:输出N个数字,分别表示A,B,C……所代表的数字,相邻的两个数字用一个空格隔开,不能有多余的空格。

样例1

样例输入1[复制]

5ABCEDBDACEEBBAA

样例输出1[复制]

1 0 3 4 2

限制

每个测试点1s

来源

NOIp 2004


解析:我将要讲的做法是深搜+剪枝,关于高斯消元解方程组的做法可以参考一下两个链接:

         http://www.type00a.com/?wpfb_dl=65

         http://maskray.me/blog/2009-11-23-noip-2004-cryptarithmetic

             下面是我的深搜思路:

          1.考虑到进位处理,所以我们搜索时是从右往左,搜索每列的字母。我们用a、b、c储存输入数据,即a+b=c,搜索到第 i 列时,先搜索a[i],在搜索b[i],并且用一个函数 OK() 判断当前搜索是否合法。

             如果a[i]、b[i]的值已知,那么c[i]的值也就知道了,就没必要枚举了。

          2.倒序枚举。这并不是什么投机取巧的做法,也不是针对某以内特殊数据。

             观察竖式,我们发现,最高位的两个数是不能产生进位的,而最低位确实可以的,这说明了什么?这说明在很大概率上,最高位的数字都是比较小的,而最低位的数字可以很大,所以才要倒序枚举。

         3.假设枚举到第 x 列,上一列对第 x 列的进位记为jinwei,然后枚举得到个字母的值,然后就要判断这个字母的值在当前以优质的条件下是否合法,我们用函数 ok(x,jinwei)来判断(此函数可以说就是本题最重要的剪枝了)。

         bool (int x,int jinwei)

         {

           if(a[0]+b[0]>=n)返回0;

           for(i=x;i>=0;i--)

              如果当前位的a[i]、b[i]已知

                 {

                   若c[i]已知,但是(a[i]+b[i])%n!=c[i],则返回0

                   若c[i]未知,但是由a[i]、b[i]算出的c[i]值已经被用过了,返回0,否则的话,算出c[i]的值

                 }

           esle 终止循环 (因为再往后的话,进位就无法确定了)   

         }


           if(a[0]+b[0]>=n)返回0;

          

         那么接下来是对于0 到 i 的处理:

          for(;i>=0;i--)

             {

                如果a[i]、b[i]都已知,那么c[i]的值有两种情况:(a[i]+b[i])%n ,(a[i]+b[i]+1)%n。若c[i]的值已知,但是却不等于这两个之中的任何一个,返回0;若c[i]的值未知,但是这两个值都被使用过了,返回0。

                若a[i]、b[i]中仅有一个的值是已知的(在这里假设a[i]的值已知),并且c[i]的值也是已知的,那么b[i]的值就有两种情况:(c[i]-a[i]+n)%n,(c[i]-a[i]-1)%n,如果这两个值都是被使用过的,返回0;  

             } 

          

         4.dfs(int x,int jinwei,bool flaga)表示现在枚举第n-1列,上一列的进位为jinwei,若flaga为1,则当前是枚举a[x]的值,否则是枚举b[x]的值。

           但是要注意的是,搜索一般都是遵循以下模式:

           标记--->dfs--->清除标记

           每次搜索时,我们枚举确定a[x]的值(这里假定是在枚举a[x]的值),并把它标记为使用过,然后我们在用ok()函数判断合法性的时候,由 a+b=? 这种情况确定了另一个尚未枚举的字母的值,我们暂时将这些由ok()函数确定的字母值称为衍生值,并用数组记录下来。

           如果对a[x]的当前值搜索失败,那么在将a[x]的当前值标记为未使用时,还要将所有的由a[x]的当前值所产生的衍生值全部清零。    

          好了,基本思路就是这些了。   

代码:

#include<cstdio>#include<cstring>using namespace std;const int maxn=26;char a[maxn+20],b[maxn+20],c[maxn+20];int n,ans[200],q[maxn+20];bool used[maxn+20];bool ok(int x,int last){  if(ans[a[0]]+ans[b[0]]>=n)return 0;  int i,j,k;  for(i=x;i>=0;i--)    if(ans[a[i]]!=-1 && ans[b[i]]!=-1)      {        j=ans[a[i]]+ans[b[i]]+last,last=j/n;        if(ans[c[i]]!=-1 && ans[c[i]]!=(j%n))return 0;        if(ans[c[i]]==-1)          {            if(used[j%n])return 0;            ans[c[i]]=j%n,used[j%n]=1;            q[++q[0]]=c[i];  }  }else break;  if(ans[a[0]]+ans[b[0]]>=n)return 0;  for(;i>=0;i--)    {      if(ans[a[i]]!=-1 && ans[b[i]]!=-1)        {          j=ans[a[i]]+ans[b[i]];          if(ans[c[i]]!=-1 && (j%n)!=ans[c[i]] && ((j+1)%n)!=ans[c[i]])return 0;          if(ans[c[i]]==-1 && used[j%n] && used[(j+1)%n])return 0;          continue;}  if(ans[a[i]]!=-1 && ans[c[i]]!=-1)    {      j=ans[c[i]]-ans[a[i]]+n+n;      if(used[j%n] && used[(j-1)%n])return 0;}  if(ans[b[i]]!=-1 && ans[c[i]]!=-1)    {      j=ans[c[i]]-ans[b[i]]+n+n;      if(used[j%n] && used[(j-1)%n])return 0;}}  return 1; }bool dfs(int x,int jinwei,bool flaga){  if(x<0)return 1;    int p=q[0],i,k;  if(flaga)    {      if(ans[a[x]]!=-1)return dfs(x,jinwei,0);      for(ans[a[x]]=n-1;ans[a[x]]>=0;ans[a[x]]--)        if(!used[ans[a[x]]])          {            used[ans[a[x]]]=1;            if(ok(x,jinwei) && dfs(x,jinwei,0))return 1;            while(q[0]>p)  {    k=q[q[0]],used[ans[k]]=0;ans[k]=-1,q[0]--;      }used[ans[a[x]]]=0;  }  return 0;}    if(ans[b[x]]!=-1)return dfs(x-1,(ans[a[x]]+ans[b[x]]+jinwei)/n,1);  for(ans[b[x]]=n-1;ans[b[x]]>=0;ans[b[x]]--)    if(!used[ans[b[x]]])      {        used[ans[b[x]]]=1;        if(ok(x,jinwei) && dfs(x-1,(ans[a[x]]+ans[b[x]]+jinwei)/n,1))return 1;        while(q[0]>p)          {           k=q[q[0]],used[ans[k]]=0;ans[k]=-1,q[0]--;  }        used[ans[b[x]]]=0;  }  return 0;}int main(){  int i;  scanf("%d%s%s%s",&n,a,b,c);  for(i=0;i<n;i++)ans['A'+i]=-1;  dfs(n-1,0,1);  printf("%d",ans['A']);  for(i=1;i<n;i++)printf(" %d",ans['A'+i]);  return 0;}



0 0