https://www.luogu.org/problemnew/show/P1092

这题如果直接暴力枚举的话,复杂度(26!)=4.0329146112661e+26.

然后用科学的剪枝居然玄学地下降到不足1e+8???

剪枝1:若加数最高位加起来大于n,则剪掉,因为最高位不能进位。

剪枝2:因为对于每一位,来自右一位的进位要么是0,要么是1,所以如果某位(两个加数相加再+1或者+0)%n都不等于和,剪掉。

完全看了洛谷排名第一的题解,他认为靠右边数字大,靠左边数字小,故从右到左摘出来不同的字母,优先从大到小排。我就想,那么靠左数字小,从左到右摘出来不同的字母,优先从小到大排。

结果他AC,我超时一个点,下载数据,发现数据对他有利----最左边的并不那么小,而最右边确实很大,然后我小改数据,本地测就是我的程序秒出,他的过了好几秒。

怎么说呢,数据太弱了,都是靠右边的较大,靠左边的较小,但右边更极端,因此他的思路可以过。

这个程序写起来非常麻烦,变量多,关系复杂,注意的地方很多。

码农题。

#include<bits/stdc++.h>
using namespace std;

int n;
char s[4][30];
int a[30],b[30],c[30];
int p[35],vis[35],used[35],num[35];

void init()
{
	scanf("%d",&n);
	for(int i=1;i<=3;i++)scanf("%s",s[i]+1);
	for(int i=1;i<=n;i++)a[i]=s[1][i]-'A';
	for(int i=1;i<=n;i++)b[i]=s[2][i]-'A';
	for(int i=1;i<=n;i++)c[i]=s[3][i]-'A';
}

void init2()
{
	int tot=0;
	for(int i=n;i>=1;i--)
	{
		if(!vis[a[i]])
		{
			p[++tot]=a[i];
			vis[a[i]]=1;
		}
		if(!vis[b[i]])
		{
			p[++tot]=b[i];
			vis[b[i]]=1;
		}
		if(!vis[c[i]])
		{
			p[++tot]=c[i];
			vis[c[i]]=1;
		}
	}
	memset(vis,0,sizeof(vis));
} 

bool prune()    //剪枝 
{
	if(used[a[1]]&&used[b[1]]&&num[a[1]]+num[b[1]]>=n) return true;
	for(int i=n;i>=1;i--)
	{
		if(used[a[i]]&&used[b[i]]&&used[c[i]])
			if((num[a[i]]+num[b[i]])%n!=num[c[i]]&&(num[a[i]]+num[b[i]]+1)%n!=num[c[i]])
				return true;
	}
	return false;
}

bool check()    //是否合法 
{
	for(int i=n,x=0;i>=1;i--)
	{
		if((num[a[i]]+num[b[i]]+x)%n!=num[c[i]])return false;
		x=(num[a[i]]+num[b[i]]+x)/n;
	}
	return true;
}

bool dfs(int cur)    //现在应该给p数组第cur个元素找真实数字 
{
	if(prune())return false;
	if(cur==n+1&&check())return true;
	for(int i=n-1;i>=0;i--)  //对于p数组第cur个元素,从大到小代入真实数字n-1~0 
	{
		if(!vis[i])
		{
			vis[i]=1;     //某代入的数字是否用过 
			used[p[cur]]=1;    //代表字母的数字是否已被真实数字代表 
			num[p[cur]]=i;    // 代表字母的数字真实为i 
			if(dfs(cur+1))return true;
			used[p[cur]]=0;
			vis[i]=0;
		}
	} 
	return false;
}

void print()
{
	for(int i=0;i<n;i++)printf("%d ",num[i]);
	putchar('\n');
} 

void debug()
{
	//for(int i=1;i<=4;i++)printf("%d ",p[i]);
	//for(int i=1;i<=n;i++)printf("%d %d %d\n",a[i],b[i],c[i]);
}

int main()
{
	freopen("input.in","r",stdin);
	init();  //字母变数字 
	init2();  //一般右边的要进位,较大,左边较小,故靠右的优先从大到小枚举,以加速 
	dfs(1);  
	print();
	//debug();
	return 0;
}