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

P1099虫食算

描述

所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:
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[复制]

5
ABCED
BDACE
EBBAA

样例输出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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值