[NOIP2004]虫食算

总时间限制: 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;   
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值