• NOIP 2004 虫食算


    NOIP 2004 虫食算

    洛谷传送门

    JDOJ传送门

    Description

    ​ 所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:

    ​ 43#9865#045

    ​ + 8468#6633

    ​ = 44445506678

    ​ 其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是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个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解

    Input

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

    Output

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

    Sample Input

    5 ABCED BDACE EBBAA

    Sample Output

    1 0 3 4 2


    题解:

    30pts:

    生成全排列一一判断即可。实际得分可以到40.

    代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    int n;
    char s1[28],s2[28],s3[28];
    int a[28];
    int a1,a2,a3;
    int qpow(int a,int b)
    {
        int ret=1;
        while(b>0)
        {
            if(b&1)
                ret*=a;
            a*=a;
            b>>=1;
        }
        return ret;
    }
    bool check()
    {
        for(int i=0;i<n;i++)
        {
            int opt1=s1[n-i-1]-'A';
            int opt2=s2[n-i-1]-'A';
            int opt3=s3[n-i-1]-'A';
            a1+=(qpow(n,i)*(a[opt1]));
            a2+=(qpow(n,i)*(a[opt2]));
            a3+=(qpow(n,i)*(a[opt3]));
        }
        return (a1+a2==a3)?1:0;
    }
    int main()
    {
        scanf("%d",&n);
        scanf("%s%s%s",s1,s2,s3);
        for(int i=0;i<n;i++)
            a[i]=i;
        do
        {
            a1=a2=a3=0;
            if(check())
            {
                for(int i=0;i<n;i++)
                    printf("%d ",a[i]);
                return 0;
            }
        }while(next_permutation(a,a+n));
        return 0;
    }
    

    50pts,没啥区别,应该是给有心写正解但是写挂了的人准备的。(真是良心阿)

    100pts:

    正解就应该是搜索+剪枝,这个没什么好说的。重点在如何剪枝。

    关于剪枝,这里有:

    浅谈常见剪枝方式

    这里选择了两种剪枝方法:(其实都属于可行性剪枝)

    第一种,最高位不能进位。

    第二种,由于是加法,所以进位最多为1。所以如果有大于1的进位,就不合法。

    当然,这样的剪枝其实还是有一些虚的。所以我们还需要试着从枚举顺序上找一些妙妙性质然后进行优化。因为这个题搜到答案就可以停,所以越快搜到答案,算法就越优秀。

    这里采用了一种77怪怪的枚举方式:设立next数组存枚举顺序。

    先放代码,结合代码理解:

    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    int n;
    char s1[28],s2[28],s3[28];
    int a[28],b[28],c[28];
    int nxt[28],cnt;
    int mp[28];
    int v[28];
    void get(int x)
    {
        if(!v[x])
        {
            v[x]=1;
            nxt[cnt++]=x;
        }
    }
    int check()
    {
        int x=0;
        for(int i=n-1;i>=0;i--)
        {
            int aa=mp[a[i]],bb=mp[b[i]],cc=mp[c[i]];
            if((aa+bb+x)%n!=cc)
                return 0;
            x=(aa+bb+x)/n;
        }
        return 1;
    }
    int prune()
    {
        if(mp[a[0]]+mp[b[0]]>=n)
            return 1;
        for(int i=n-1;i>=0;i--)
        {
            int aa=mp[a[i]],bb=mp[b[i]],cc=mp[c[i]];
            if(aa==-1||bb==-1||cc==-1)
                continue;
            if((aa+bb)%n!=cc&&(aa+bb+1)%n!=cc)
                return 1;
        }
        return 0;
    }
    void dfs(int x)
    {
        if(prune())
            return;
        if(x==n)
        {
            if(check())
            {
                for(int i=0;i<n-1;i++)
                    printf("%d ",mp[i]);
                printf("%d",mp[n-1]);
                exit(0);
            }
            return;
        }
        for(int i=n-1;i>=0;i--)
            if(!v[i])
            {
                v[i]=1;
                mp[nxt[x]]=i;
                dfs(x+1);
                v[i]=0;
                mp[nxt[x]]=-1;
            }
    }
    int main()
    {
        scanf("%d",&n);
        scanf("%s%s%s",s1,s2,s3);
        for(int i=0;i<n;i++)
        {
            a[i]=s1[i]-'A';
            b[i]=s2[i]-'A';
            c[i]=s3[i]-'A';
        }
        for(int i=n-1;i>=0;i--)
        {
            get(a[i]);
            get(b[i]);
            get(c[i]);
        }
        memset(mp,-1,sizeof(mp));
        memset(v,0,sizeof(v));
        dfs(0);
        return 0;
    }
    

    根据代码,我们可以得出,这个nxt数组是从右到左依次存的。那么这样存储的话,我们枚举到的东西就更能处理进位的情况,这样一来,就能优化不少复杂度。

  • 相关阅读:
    .NET开发微信公众号之创建自定义菜单
    FOR XML PATH 可以将查询结果根据行输出成XML格式
    Node入门
    javascript客户端检测技术
    理解OAuth 2.0(转)
    RESTful API 设计指南(转)
    forever让nodejs应用后台执行
    Git使用教程
    NodeJS基础教程
    windows系统下简单nodejs安装及环境配置
  • 原文地址:https://www.cnblogs.com/fusiwei/p/13828380.html
Copyright © 2020-2023  润新知