(UPC2.13)虫食算(深搜顺序变换+剪枝)

最近刷题,如果是深搜,往往是剪枝比较烦,有些甚至还要卡输入输出,不过做到的题大多是重在剪枝,深搜一般是从头开始的枚举这种无脑套路,关于剪枝,主要是剖析问题,大胆剪枝,没什么好说的,想不到的就想不到吧。

昨天刷到的一道题,倒是刷新了我对深搜方式的认识,(ps:看到网上讲解,说这题是玄学剪枝,不是很同意)因为这题重点不在剪枝,重在怎么深搜。

下面给题:

虫食算

题目描述

所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:
43#9865#045
+8468#6633
44445509678
其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是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个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解

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

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

样例输入
5
ABCED
BDACE
EBBAA

样例输出
1 0 3 4 2

题解:

扫描二维码关注公众号,回复: 11936067 查看本文章

剪枝的两个点很容易看出。
1、A,B首位相加不能进位
2、若A,B,C的第i位已知,(A+B)%n 或(A+B+1)%n 不低于 C,则不成立(+1是考虑进位情况)

最后的总判也很简单,从末到首,存在相加不等则不成立。

下面赋上我一开始的代码:

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;

int n,a[30],b[30],c[30];
bool flag=0;//总判断 
bool f[30];//表示i这个数是否被用过 
int num[30];//num[i]表示i这个字母表示的数 
ll sum;
 
bool judge(int x){
    
    
	for(int i=0;i<n;i++){
    
    
		if(a[i]<x&&b[i]<x&&c[i]<x){
    
    			//第i位的三个数都取到值时 
			int t1=num[a[i]]+num[b[i]];
			int t2=t1+1;					//考虑进位 
			int t3=num[c[i]];
			t1%=n;
			t2%=n;
			if(t1!=t3&&t2!=t3)	return 1;	//t1,t2若都不等于t3,则不成立 
		}
	}
	return 0;
}

bool add(){
    
    
	int t=0;		//用于进位
	for(int i=n-1;i>=0;i--){
    
    
		int temp=num[a[i]]+num[b[i]]+t;
		if(temp%n!=num[c[i]])	return 0;
		t=temp/n;
	}
	return 1;
}

void dfs(int len){
    
    
	//深搜结束 
	if(len==n){
    
    
		if(add()){
    
    		//相加判断 
			flag=1;
			return;
		}
		return;
	}
	//剪枝一 
	if(a[0]<len&&b[0]<len){
    
    					//首位都取到值时 
		if(num[a[0]]+num[b[0]]>=n)	return;	//首位相加若进位,则不成立。 
	}
	//剪枝二
	if(judge(len))	return;
	//剪枝三 
	if(flag)	return;
	//深搜继续 
	for(int i=n-1;i>=0;i--){
    
    		//从高位开始取 
		if(flag)	return; 
		if(!f[i]){
    
    
	        num[len]=i;
        	f[i]=1;
        sum++;
        	dfs(len+1);
        	f[i]=0;
    	}
	}
    
}



int main(){
    
    
	//输入并转化 
    cin>>n;
    getchar();
    for(int i=1;i<=3;i++){
    
    
    	string temp;
    	getline(cin,temp);
    	if(i==1)	for(int j=0;j<n;j++)	a[j]=temp[j]-'A';
    	else if(i==2)	for(int j=0;j<n;j++)	b[j]=temp[j]-'A';
    	else if(i==3)	for(int j=0;j<n;j++)	c[j]=temp[j]-'A';
	}
	
    dfs(0);
    //输出 
    for(int i=0;i<n;i++){
    
    
    	if(i==0)	cout<<num[i];
    	else	cout<<' '<<num[i];
	}
	cout << endl << sum << endl;
}

首先思路很清晰可以看出。(下面给出化简版)

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;

bool flag=0;//总判断 
bool judge(int x){
    
    }
bool add(){
    
    }

void dfs(int len){
    
    
	//深搜结束 
	if(len==n){
    
    }
	//剪枝一 
	if(a[0]<len&&b[0]<len){
    
    }
	//剪枝二
	if(judge(len))	return;
	//剪枝三 
	if(flag)	return;
	//深搜继续 
	for(int i=0;i<n;i++){
    
    }
}

int main(){
    
    
	//输入并转化 
    cin>>n;
    for(int i=1;i<=3;i++){
    
    }
	
    dfs(0);
    //输出 
    for(int i=0;i<n;i++){
    
    }
  //cout << endl << sum << endl;
}

这里我的深搜方式,是从头开始枚举,先给A赋值,再B,C,D等等。判断一个字母是否被赋值,只要看深搜深度len。

可是这样超时。

答案给的深搜方式不是这种无脑流,他多了下面一个操作。

    for(int i=n-1;i>=0;i--){
    
    						//记录搜索的字母顺序 
        if(!f[a[i]]) arr[cnt++]=a[i],f[a[i]]=1;
        if(!f[b[i]]) arr[cnt++]=b[i],f[b[i]]=1;
        if(!f[c[i]]) arr[cnt++]=c[i],f[c[i]]=1;
        if(cnt==n) break;
    }
    //for(int i=0;i<n;i++) printf("%d ",arr[i]);

不是按递增顺序,而是按字母出现的顺序,比如:案例一中,先给D赋值,再E,A,B,C, A (重复,省略)等等,从后往前(从前往后也可以),这样操作后,赋三个值后,就可以判断剪枝二,原来的方法至少赋值3个,最多n个,

5
ABCED
BDACE
EBBAA

接来下比较一下复杂度,因为不好看出,我用深搜的次数来看看,

案例一中,原方法回溯76次,而这个12次。

自己有凑了个案例二
7
ABCDEFG
GCABGAF
AEDFFGF

1 2 3 4 5 6 0

原方法回溯2101次,而这个88次。

变换深搜顺序后,相应代码也可以稍微改改,下面附上AC代码

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
using namespace std;

int n,cnt;
int a[30],b[30],c[30];
bool f[30];//表示i这个数是否被用过 
int num[30];//num[i]表示i这个字母表示的数 
int arr[30];
bool flag;
//ll sum;
bool judge(){
    
    
	for(int i=0;i<n;i++){
    
    
		int A=num[a[i]],B=num[b[i]],C=num[c[i]];
		if(A!=-1&&B!=-1&&C!=-1){
    
    			//第i位的三个数都取到值时 
			int t1=(A+B)%n;
			int t2=(A+B+1)%n;				//考虑进位 
			if(t1!=C&&t2!=C)	return 1;	//t1,t2若都不等于C,则不成立 
		}
	}
	return 0;
}

bool add(){
    
    
	int t=0;		//用于进位
	for(int i=n-1;i>=0;i--){
    
    
		int temp=num[a[i]]+num[b[i]]+t;
		if(temp%n!=num[c[i]])	return 0;
		t=temp/n;
	}
	return 1;
}

void dfs(int len){
    
    			//搜索第len个字母是哪个数 
	//深搜结束 
	if(len==n){
    
    
		if(add()){
    
    		//相加判断 
			flag=1;
			return;
		}
		return;
	}
	//剪枝一 
    if(num[a[0]]+num[b[0]]>=n) return;
    //剪枝二
	if(judge())	return;
	//剪枝三 
	if(flag)	return;

    for(int i=n-1;i>=0;i--){
    
    
    	if(!f[i]){
    
    
        	num[arr[len]]=i;
        	f[i]=1;
//        	sum++;
    	    dfs(len+1);
    	    if(flag)	return;
	        f[i]=0;
        	num[arr[len]]=-1; 
    	}
	} 
}
int main(){
    
    
    //输入并转化 
    cin>>n;
    for(int i=1;i<=3;i++){
    
    
    	string temp;
    	cin>>temp;
    	if(i==1)	for(int j=0;j<n;j++)	a[j]=temp[j]-'A';
    	else if(i==2)	for(int j=0;j<n;j++)	b[j]=temp[j]-'A';
    	else if(i==3)	for(int j=0;j<n;j++)	c[j]=temp[j]-'A';
	}

    for(int i=n-1;i>=0;i--){
    
    						//记录搜索的字母顺序 
        if(!f[a[i]]) arr[cnt++]=a[i],f[a[i]]=1;
        if(!f[b[i]]) arr[cnt++]=b[i],f[b[i]]=1;
        if(!f[c[i]]) arr[cnt++]=c[i],f[c[i]]=1;
        if(cnt==n) break;
    }
    
    //for(int i=0;i<n;i++) printf("%d ",arr[i]);
    memset(f,false,sizeof(f));
    memset(num,-1,sizeof(num));
    
    
    dfs(0);
    for(int i=0;i<n;i++){
    
    
    	if(i==0)	cout<<num[i];
    	else	cout<<' '<<num[i];
	}
//	cout << endl << sum << endl;
}

猜你喜欢

转载自blog.csdn.net/weixin_45606191/article/details/104307803
今日推荐