P1092 [NOIP 2004 提高组] 虫食算

题目传送门

我的博客


一.30分暴力

首先 30 分暴力很简单,使用dfs枚举 \(0\) ~ \(n-1\) 的排列,并在枚举到一个排列时判断是否合法就行了。

这里贴个代码,因为后面代码都是有关dfs的剪枝。

P1092_30分暴力
#include<bits/stdc++.h>
#define int long long
using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<48){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}

inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x<10) putchar(x+'0');
	else write(x/10),putchar(x%10+'0');
}

const int N=30;
int n,p[N],ans,vis[N],fl;
char s1[N],s2[N],s3[N];

inline bool chk(){
	int jw=0;
	for(int i=n;i>=1;i--){
		int dq1=p[(int)(s1[i]-'A'+1)];
		int dq2=p[(int)(s2[i]-'A'+1)];
		int dq3=p[(int)(s3[i]-'A'+1)];
		if(dq1+dq2+jw==dq3){
			jw=0;
		}
		else if(dq1+dq2+jw==dq3+n){
			jw=1;
		}
		else{
			return 0;
		}
	}
	if(jw==1){
		return 0;
	}
	else{
		return 1;
	}
}

inline void dfs(int pos){
	if(fl) return ;
	if(pos==n){
		int res=chk();
		if(res){
			for(int i=1;i<=n;i++){
				printf("%lld ",p[i]);
			}
			fl=1;return ;
		}
	}
	for(int i=n-1;i>=0;i--){
		if(!vis[i]){
			vis[i]=1;
			p[pos+1]=i;
			dfs(pos+1);
			vis[i]=0;
		}
	}
}

signed main(){
	n=read();
	scanf("%s%s%s",s1+1,s2+1,s3+1);
	dfs(0);
	return 0;
}

这个暴力哪都好,就是讨论了很多种无用的情况,所以我们给它剪剪枝。


二.正解

剪枝一

首先易得,两个 \(n\) 位数相加还是 \(n\) 位数,说明最高位肯定是没有进位的,也就是 \(jw=0\)

但这个好像没什么太大用,我们接着考虑一点别的剪枝。

剪枝二

上面这个代码是枚举完排列才判断是否合法的,那有没有一种方法能在查找排列过程中就判断是否合法呢?

有的兄弟,有的。

我们把三个数看做一个 \(3 \times n\) 的矩阵,然后从上往下,从低位往高位去填每个字母可能对应的数字。

(下面的 \(num1,num2,num3\) 分别表示对于某一列来说,第一行的数字,第二行的数字,第三行的数字)

首先对于某一位,无解情况的判断肯定是填到第 3 行进行的。(不然呢?)

其次,如果这一位里你填到第三行,发现第一行+第二行+上一位进位的结果与第三行不相同(即 $(num1+num2+jw) \mod n \neq num3 $ ),那肯定是无解的,直接返回就行。

剪枝三

除此以外,我们有第三个剪枝优化:

由于相同字母对应的数相同,所以如果我们枚举低位字母对应的数,那么高位的一些字母也能提前转化成数。

这样我们填到第 \(y\) 列的时候,就可以提前判断一下高位 \(1\) ~ \(y-1\) 是否有可能无解了。

虽然我们不清楚高位的进制情况,但由于在 \(n\) 进制加法里, \(jw\) 的取值只有 \(0\)\(1\) 两种,所以说高位 \(num3\) 的取值只有两种: \((num1+num2) \mod n\)\((num1+num2+1) \mod n\)

这样在 \(num1,num2,num3\) 都确定的情况下,我们就可以判断高位的合法性了。

总结

总结一下,相较于上面暴力代码,我们这次是从右往左,从上往下枚举了每一个位置字母的取值,每当有一个新取值就给它记录下来,并增加了三个剪枝:

1.最高位计算完后的进位一定是 \(0\)

2.对于第三行一个填完的数,如果 $(num1+num2+jw) \mod n \neq num3 $ ,则无解。

3.对于第 \(y\) 列来说, \(1\) ~ \(y-1\) 的某个高位如果满足 三个字母都有取值 且 \((num1+num2) \mod n \neq num3\) && $(num1+num2+1) \mod n \neq num3 $ ,则无解。

这样这个题的搜索做法就有了。

代码:

P1092_搜索
#include<bits/stdc++.h>
#define int long long
using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<48){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}

inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x<10) putchar(x+'0');
	else write(x/10),putchar(x%10+'0');
}

const int N=30;
int n,cp[N],ans;
char a[4][N];
map<char,int> mp;
//mp[c]:字符c对应的数字是几 
//cp[i]:数字i是否被字符匹配 

inline bool chk(int pos){//判某个高位是否非法的 
	if(mp[a[1][pos]]!=-1&&mp[a[2][pos]]!=-1&&mp[a[3][pos]]!=-1&&(mp[a[1][pos]]+mp[a[2][pos]])%n!=mp[a[3][pos]]&&(mp[a[1][pos]]+mp[a[2][pos]]+1)%n!=mp[a[3][pos]]){
		//前三个条件表示该位三个数是否都填过了,后两个表示num3的值是否合法 
		return 0;
	}
	return 1;
}

inline void dfs(int x,int y,int jw){
	//现在填第x行,第y列,上一位进位是jw
	if(y==0&&x==1&&jw==0){
		//找到答案 
		for(char i='A';i<='A'+n-1;i++){
			printf("%lld ",mp[i]);
		}
		exit(0);
	}
	if(mp[a[x][y]]!=-1){//当前位置的字母已经有数了 
		if(x==3){
			if((mp[a[1][y]]+mp[a[2][y]]+jw)%n!=mp[a[3][y]]){
				//当前位置的数非法 
				return ;
			}
			else{
				//当前这一位填完了,我们去填它上一位的第一个数 
				dfs(1,y-1,(mp[a[1][y]]+mp[a[2][y]]+jw)/n);
			}
		}
		else{
			//说明当前这一位没填完,接着填 
			dfs(x+1,y,jw);
		}
	}
	else{//给当前字母填数
		for(int i=1;i<y;i++){
			if(!chk(i)){//高位非法 
				return ;
			}
		}
		for(int i=0;i<n;i++){
			if(!cp[i]){
				cp[i]=1;mp[a[x][y]]=i;
				if(x==3){
					if((mp[a[1][y]]+mp[a[2][y]]+jw)%n!=mp[a[3][y]]){
						//填不进去就换个数填 
						cp[i]=0;mp[a[x][y]]=-1;
						continue;
					}
					else{//同上 
						dfs(1,y-1,(mp[a[1][y]]+mp[a[2][y]]+jw)/n);
					}
				}
				else{//同上 
					dfs(x+1,y,jw);
				}
				//记得回溯 
				cp[i]=0;mp[a[x][y]]=-1;
			}
		}
	}
}

signed main(){
	n=read();
	for(char i='A';i<='Z';i++){
		mp[i]=-1;
	}
	for(int j=1;j<=3;j++){
		for(int i=1;i<=n;i++){
			cin>>a[j][i];
		}
	} 
	dfs(1,n,0);
	
	//7123532712353231271571235327123532312715712353271235323127157123532623636236
	//212123532121232152121235323212221212353232163212132121532121
	//12316561767767312176565656565253123165617677673121765633563356
	//123656565236565653216112161343212365656523656565321632161163216321611
	//无奖竞赛上面四行是什么 
	
	return 0;
}

参考资料

可以去看一看这位dalao的题解qwq

posted @ 2025-10-16 21:15  qwqSW  阅读(6)  评论(0)    收藏  举报