P1092 虫食算 题解(dfs 深度优先搜索)

P1092 虫食算

  • 题目
  • 分析
  • 代码
  • 传送门

题目

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

 43#9865#045
+  8468#6633
 44445509678

其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是55和33,第二行的数字是55。

现在,我们对问题做两个限制:

首先,我们只考虑加法的虫食算。这里的加法是NN进制加法,算式中三个数都有NN位,允许有前导的00。

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

 BADC
+CBDA
 DCCC

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

输入格式
包含四行。
第一行有一个正整数N(N \le 26)N(N≤26)。

后面的三行,每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这3个字符串左右两端都没有空格,从高位到低位,并且恰好有NN位。

输出格式
一行,即唯一的那组解。

解是这样表示的:输出NN个数字,分别表示A,B,C,…A,B,C,…所代表的数字,相邻的两个数字用一个空格隔开,不能有多余的空格。

输入输出样例
输入

5
ABCED
BDACE
EBBAA

输出

1 0 3 4 2

说明/提示
对于30%的数据,保证有N \le 10N≤10;

对于50%的数据,保证有N \le 15N≤15;

对于全部的数据,保证有N \le 26N≤26。

分析

我刚开始是一个字母一个字母去判断,尽管做了部分剪枝,依然存在超时。我最初的思路是用一个map去装载每个单词及对应的值,然后dfs全排列对字母进行组合,唯一的剪枝操作是对前两个式子之和与结果每一位进行对比,但是显然不够。最后发现有人在已知三个字母时就开始判断这一列,之和不等于结果就可以直接pass。这样,效率就可以大大提升。

详细解体思路请见代码的注释。

代码

超时代码

#include
#include
#include
#include
using namespace std;

int n;
string f[3];
string word = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
map<char,int> num; //使用map记录单词和其对应的值
int vis[30];

bool check(){
	int sum1 = 0;
	int sum2 = 0;
	int add = 0;
	bool ad = false; //进位判定
	for(int i = n-1;i >= 0;i--){
		int s = num.find(f[0][i])->second + num.find(f[1][i])->second + add; //前两个式子的和
		if(s >= n){ //进位
			s -= n;
			ad = true;
		}
			
		if(s != num.find(f[2][i])->second) 
			return false;
			
		sum1 += s*(10^(n-i-1));
		if(ad){
			add = 1;
			ad = false;
		}
		
		sum2 += num.find(f[2][i])->second*(10^(n-i-1));
	}
	
	return true;
}

void dfs(int r){

	if(r >= n){
		
		if(check()){
			for(int i = 0;i < n;i++){
				if(i != 0){
					printf(" %d",num[word[i]]);
				}else printf("%d",num[word[i]]);
			}
				
			cout<<endl;
			exit(0);
		}else return;
		
	}
	
	for(int i = n-1;i >= 0;i--){
		if(!vis[i]){
			
			vis[i] = 1;
			num[word[r]] = i; //记录每个字母对应的值
			
			dfs(r+1);
			
			num[word[r]] = -1;
			vis[i] = 0;
			
		}
	}
	
}

int main(){
	scanf("%d",&n);
	for(int i = 0;i < 3;i++)
		cin>>f[i];
	
	for(int i = 0;i < n;i++){
		num[word[i]] = -1;
	}	
	
	dfs(0);
	
	return 0;
} 

ac代码

#include
#include

const int N = 30;
int n;
int a[N],b[N],c[N]; //记录三个式子中出现的字母 
int vis[N]; //访问标记 
int ans[N];
char s[3][N];
int seq[N]; //搜索顺序按照第一个字符串的顺序 
void dfs(int r){//搜到第x个元素 
    int i,jw=0;
    
    if(ans[a[0]]+ans[b[0]] >= n)return;
    
    for(i = n-1;i >= 0;i--){//从后往前检查 
        int A=ans[a[i]];
		int B=ans[b[i]];
		int C=ans[c[i]];
        
        if(A==-1||B==-1||C==-1) 
			continue; //当已有三个字母已知时,进行剪枝操作 
        
        if((A+B+1)%n != C && (A+B)%n != C) //在已知的三个字母中进行判断,前两个式子之和进位后或未进位的值不等于不等于第三个式子的可以直接排除 
			return;
    }
    
    if(r == n){
        for(i = n-1;i >= 0;i--){
            int A=ans[a[i]];
			int B=ans[b[i]];
			int C=ans[c[i]];
            if((A+B+jw)%n != C) 
				return;
            
            jw = (A+B+jw)/n;
        }
        
        for(i = 0;i < n;i++) 
			printf("%d ",ans[i]);
        exit(0); //输出后,退出程序,避免继续运行导致超时 
    }
    
    for(i = n-1;i >= 0;i--){ //逆序遍历 
        if(!vis[i]){
            vis[i]=1;
			ans[seq[r]]=i; //记录每个字母对应的值 
			
            dfs(r+1);
            
            ans[seq[r]]=-1;
            vis[i]=0;
        }
    }
}
int cnt;
void f(int i){ //去重 
    if(!vis[i]){
		vis[i]=1;seq[cnt++] = i;
	}
}
int main(){
    int i;
    for(i = 0;i < 30;i++)
		ans[i] = -1;
    
    scanf("%d%s%s%s",&n,s[0],s[1],s[2]);
    
    for(i = 0;i < n;i++){ //记录前n-1个单词 
        a[i] = s[0][i]-'A';
        b[i] = s[1][i]-'A';
        c[i] = s[2][i]-'A';
    }
    
    for(i = n-1;i >= 0;i--){ //去重 
		f(a[i]);
		f(b[i]);
		f(c[i]);
	}
    for(i = 0;i < n;i++)
		vis[i]=0;
     
    dfs(0);
    
    return 0;
}

传送门

P1092

你可能感兴趣的:(P1092 虫食算 题解(dfs 深度优先搜索))