noip2004 虫食算 (深搜,倒序枚举+高斯消元解方程组)
2015-09-01 10:39
281 查看
P1099虫食算
Accepted
标签:搜索
搜索与剪枝NOIP提高组2004
所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:
43#9865#045
+ 8468#6633
= 44445506678
其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是5和3,第二行的数字是5。
现在,我们对问题做两个限制:
首先,我们只考虑加法的虫食算。这里的加法是N进制加法,算式中三个数都有N位,允许有前导的0。
其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。如果这个算式是N进制的,我们就取英文字母表午的前N个大写字母来表示这个算式中的0到N-1这N个不同的数字:但是这N个字母并不一定顺序地代表0到N-1)。输入数据保证N个字母分别至少出现一次。
BADC
+ CRDA
= DCCC
上面的算式是一个4进制的算式。很显然,我们只要让ABCD分别代表0123,便可以让这个式子成立了。你的任务是,对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解
每个测试点1s
NOIp 2004
解析:我将要讲的做法是深搜+剪枝,关于高斯消元解方程组的做法可以参考一下两个链接:
http://www.type00a.com/?wpfb_dl=65
http://maskray.me/blog/2009-11-23-noip-2004-cryptarithmetic
下面是我的深搜思路:
1.考虑到进位处理,所以我们搜索时是从右往左,搜索每列的字母。我们用a、b、c储存输入数据,即a+b=c,搜索到第 i 列时,先搜索a[i],在搜索b[i],并且用一个函数 OK() 判断当前搜索是否合法。
如果a[i]、b[i]的值已知,那么c[i]的值也就知道了,就没必要枚举了。
2.倒序枚举。这并不是什么投机取巧的做法,也不是针对某以内特殊数据。
观察竖式,我们发现,最高位的两个数是不能产生进位的,而最低位确实可以的,这说明了什么?这说明在很大概率上,最高位的数字都是比较小的,而最低位的数字可以很大,所以才要倒序枚举。
3.假设枚举到第 x 列,上一列对第 x 列的进位记为jinwei,然后枚举得到个字母的值,然后就要判断这个字母的值在当前以优质的条件下是否合法,我们用函数 ok(x,jinwei)来判断(此函数可以说就是本题最重要的剪枝了)。
bool (int x,int jinwei)
{
if(a[0]+b[0]>=n)返回0;
for(i=x;i>=0;i--)
如果当前位的a[i]、b[i]已知
{
若c[i]已知,但是(a[i]+b[i])%n!=c[i],则返回0
若c[i]未知,但是由a[i]、b[i]算出的c[i]值已经被用过了,返回0,否则的话,算出c[i]的值
}
esle 终止循环 (因为再往后的话,进位就无法确定了)
}
if(a[0]+b[0]>=n)返回0;
那么接下来是对于0 到 i 的处理:
for(;i>=0;i--)
{
如果a[i]、b[i]都已知,那么c[i]的值有两种情况:(a[i]+b[i])%n ,(a[i]+b[i]+1)%n。若c[i]的值已知,但是却不等于这两个之中的任何一个,返回0;若c[i]的值未知,但是这两个值都被使用过了,返回0。
若a[i]、b[i]中仅有一个的值是已知的(在这里假设a[i]的值已知),并且c[i]的值也是已知的,那么b[i]的值就有两种情况:(c[i]-a[i]+n)%n,(c[i]-a[i]-1)%n,如果这两个值都是被使用过的,返回0;
}
4.dfs(int x,int jinwei,bool flaga)表示现在枚举第n-1列,上一列的进位为jinwei,若flaga为1,则当前是枚举a[x]的值,否则是枚举b[x]的值。
但是要注意的是,搜索一般都是遵循以下模式:
标记--->dfs--->清除标记
每次搜索时,我们枚举确定a[x]的值(这里假定是在枚举a[x]的值),并把它标记为使用过,然后我们在用ok()函数判断合法性的时候,由 a+b=? 这种情况确定了另一个尚未枚举的字母的值,我们暂时将这些由ok()函数确定的字母值称为衍生值,并用数组记录下来。
如果对a[x]的当前值搜索失败,那么在将a[x]的当前值标记为未使用时,还要将所有的由a[x]的当前值所产生的衍生值全部清零。
好了,基本思路就是这些了。
代码:
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=26;
char a[maxn+20],b[maxn+20],c[maxn+20];
int n,ans[200],q[maxn+20];
bool used[maxn+20];
bool ok(int x,int last)
{
if(ans[a[0]]+ans[b[0]]>=n)return 0;
int i,j,k;
for(i=x;i>=0;i--)
if(ans[a[i]]!=-1 && ans[b[i]]!=-1)
{
j=ans[a[i]]+ans[b[i]]+last,last=j/n;
if(ans[c[i]]!=-1 && ans[c[i]]!=(j%n))return 0;
if(ans[c[i]]==-1)
{
if(used[j%n])return 0;
ans[c[i]]=j%n,used[j%n]=1;
q[++q[0]]=c[i];
}
}
else break;
if(ans[a[0]]+ans[b[0]]>=n)return 0;
for(;i>=0;i--)
{
if(ans[a[i]]!=-1 && ans[b[i]]!=-1)
{
j=ans[a[i]]+ans[b[i]];
if(ans[c[i]]!=-1 && (j%n)!=ans[c[i]] && ((j+1)%n)!=ans[c[i]])return 0;
if(ans[c[i]]==-1 && used[j%n] && used[(j+1)%n])return 0;
continue;
}
if(ans[a[i]]!=-1 && ans[c[i]]!=-1)
{
j=ans[c[i]]-ans[a[i]]+n+n;
if(used[j%n] && used[(j-1)%n])return 0;
}
if(ans[b[i]]!=-1 && ans[c[i]]!=-1)
{
j=ans[c[i]]-ans[b[i]]+n+n;
if(used[j%n] && used[(j-1)%n])return 0;
}
}
return 1;
}
bool dfs(int x,int jinwei,bool flaga)
{
if(x<0)return 1;
int p=q[0],i,k;
if(flaga)
{
if(ans[a[x]]!=-1)return dfs(x,jinwei,0);
for(ans[a[x]]=n-1;ans[a[x]]>=0;ans[a[x]]--)
if(!used[ans[a[x]]])
{
used[ans[a[x]]]=1;
if(ok(x,jinwei) && dfs(x,jinwei,0))return 1;
while(q[0]>p)
{
k=q[q[0]],used[ans[k]]=0;
ans[k]=-1,q[0]--;
}
used[ans[a[x]]]=0;
}
return 0;
}
if(ans[b[x]]!=-1)return dfs(x-1,(ans[a[x]]+ans[b[x]]+jinwei)/n,1);
for(ans[b[x]]=n-1;ans[b[x]]>=0;ans[b[x]]--)
if(!used[ans[b[x]]])
{
used[ans[b[x]]]=1;
if(ok(x,jinwei) && dfs(x-1,(ans[a[x]]+ans[b[x]]+jinwei)/n,1))return 1;
while(q[0]>p)
{
k=q[q[0]],used[ans[k]]=0;
ans[k]=-1,q[0]--;
}
used[ans[b[x]]]=0;
}
return 0;
}
int main()
{
int i;
scanf("%d%s%s%s",&n,a,b,c);
for(i=0;i<n;i++)ans['A'+i]=-1;
dfs(n-1,0,1);
printf("%d",ans['A']);
for(i=1;i<n;i++)printf(" %d",ans['A'+i]);
return 0;
}
Accepted
标签:搜索
搜索与剪枝NOIP提高组2004
描述
所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:43#9865#045
+ 8468#6633
= 44445506678
其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是5和3,第二行的数字是5。
现在,我们对问题做两个限制:
首先,我们只考虑加法的虫食算。这里的加法是N进制加法,算式中三个数都有N位,允许有前导的0。
其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。如果这个算式是N进制的,我们就取英文字母表午的前N个大写字母来表示这个算式中的0到N-1这N个不同的数字:但是这N个字母并不一定顺序地代表0到N-1)。输入数据保证N个字母分别至少出现一次。
BADC
+ CRDA
= DCCC
上面的算式是一个4进制的算式。很显然,我们只要让ABCD分别代表0123,便可以让这个式子成立了。你的任务是,对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解
格式
输入格式
输入包含4行。第一行有一个正整数N(N<=26),后面的3行每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这3个字符串左右两端都没有空格,从高位到低位,并且恰好有N位。输出格式
输出包含一行。在这一行中,应当包含唯一的那组解。解是这样表示的:输出N个数字,分别表示A,B,C……所代表的数字,相邻的两个数字用一个空格隔开,不能有多余的空格。
样例1
样例输入1[复制]
5 ABCED BDACE EBBAA
样例输出1[复制]
1 0 3 4 2
限制
每个测试点1s
来源
NOIp 2004解析:我将要讲的做法是深搜+剪枝,关于高斯消元解方程组的做法可以参考一下两个链接:
http://www.type00a.com/?wpfb_dl=65
http://maskray.me/blog/2009-11-23-noip-2004-cryptarithmetic
下面是我的深搜思路:
1.考虑到进位处理,所以我们搜索时是从右往左,搜索每列的字母。我们用a、b、c储存输入数据,即a+b=c,搜索到第 i 列时,先搜索a[i],在搜索b[i],并且用一个函数 OK() 判断当前搜索是否合法。
如果a[i]、b[i]的值已知,那么c[i]的值也就知道了,就没必要枚举了。
2.倒序枚举。这并不是什么投机取巧的做法,也不是针对某以内特殊数据。
观察竖式,我们发现,最高位的两个数是不能产生进位的,而最低位确实可以的,这说明了什么?这说明在很大概率上,最高位的数字都是比较小的,而最低位的数字可以很大,所以才要倒序枚举。
3.假设枚举到第 x 列,上一列对第 x 列的进位记为jinwei,然后枚举得到个字母的值,然后就要判断这个字母的值在当前以优质的条件下是否合法,我们用函数 ok(x,jinwei)来判断(此函数可以说就是本题最重要的剪枝了)。
bool (int x,int jinwei)
{
if(a[0]+b[0]>=n)返回0;
for(i=x;i>=0;i--)
如果当前位的a[i]、b[i]已知
{
若c[i]已知,但是(a[i]+b[i])%n!=c[i],则返回0
若c[i]未知,但是由a[i]、b[i]算出的c[i]值已经被用过了,返回0,否则的话,算出c[i]的值
}
esle 终止循环 (因为再往后的话,进位就无法确定了)
}
if(a[0]+b[0]>=n)返回0;
那么接下来是对于0 到 i 的处理:
for(;i>=0;i--)
{
如果a[i]、b[i]都已知,那么c[i]的值有两种情况:(a[i]+b[i])%n ,(a[i]+b[i]+1)%n。若c[i]的值已知,但是却不等于这两个之中的任何一个,返回0;若c[i]的值未知,但是这两个值都被使用过了,返回0。
若a[i]、b[i]中仅有一个的值是已知的(在这里假设a[i]的值已知),并且c[i]的值也是已知的,那么b[i]的值就有两种情况:(c[i]-a[i]+n)%n,(c[i]-a[i]-1)%n,如果这两个值都是被使用过的,返回0;
}
4.dfs(int x,int jinwei,bool flaga)表示现在枚举第n-1列,上一列的进位为jinwei,若flaga为1,则当前是枚举a[x]的值,否则是枚举b[x]的值。
但是要注意的是,搜索一般都是遵循以下模式:
标记--->dfs--->清除标记
每次搜索时,我们枚举确定a[x]的值(这里假定是在枚举a[x]的值),并把它标记为使用过,然后我们在用ok()函数判断合法性的时候,由 a+b=? 这种情况确定了另一个尚未枚举的字母的值,我们暂时将这些由ok()函数确定的字母值称为衍生值,并用数组记录下来。
如果对a[x]的当前值搜索失败,那么在将a[x]的当前值标记为未使用时,还要将所有的由a[x]的当前值所产生的衍生值全部清零。
好了,基本思路就是这些了。
代码:
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=26;
char a[maxn+20],b[maxn+20],c[maxn+20];
int n,ans[200],q[maxn+20];
bool used[maxn+20];
bool ok(int x,int last)
{
if(ans[a[0]]+ans[b[0]]>=n)return 0;
int i,j,k;
for(i=x;i>=0;i--)
if(ans[a[i]]!=-1 && ans[b[i]]!=-1)
{
j=ans[a[i]]+ans[b[i]]+last,last=j/n;
if(ans[c[i]]!=-1 && ans[c[i]]!=(j%n))return 0;
if(ans[c[i]]==-1)
{
if(used[j%n])return 0;
ans[c[i]]=j%n,used[j%n]=1;
q[++q[0]]=c[i];
}
}
else break;
if(ans[a[0]]+ans[b[0]]>=n)return 0;
for(;i>=0;i--)
{
if(ans[a[i]]!=-1 && ans[b[i]]!=-1)
{
j=ans[a[i]]+ans[b[i]];
if(ans[c[i]]!=-1 && (j%n)!=ans[c[i]] && ((j+1)%n)!=ans[c[i]])return 0;
if(ans[c[i]]==-1 && used[j%n] && used[(j+1)%n])return 0;
continue;
}
if(ans[a[i]]!=-1 && ans[c[i]]!=-1)
{
j=ans[c[i]]-ans[a[i]]+n+n;
if(used[j%n] && used[(j-1)%n])return 0;
}
if(ans[b[i]]!=-1 && ans[c[i]]!=-1)
{
j=ans[c[i]]-ans[b[i]]+n+n;
if(used[j%n] && used[(j-1)%n])return 0;
}
}
return 1;
}
bool dfs(int x,int jinwei,bool flaga)
{
if(x<0)return 1;
int p=q[0],i,k;
if(flaga)
{
if(ans[a[x]]!=-1)return dfs(x,jinwei,0);
for(ans[a[x]]=n-1;ans[a[x]]>=0;ans[a[x]]--)
if(!used[ans[a[x]]])
{
used[ans[a[x]]]=1;
if(ok(x,jinwei) && dfs(x,jinwei,0))return 1;
while(q[0]>p)
{
k=q[q[0]],used[ans[k]]=0;
ans[k]=-1,q[0]--;
}
used[ans[a[x]]]=0;
}
return 0;
}
if(ans[b[x]]!=-1)return dfs(x-1,(ans[a[x]]+ans[b[x]]+jinwei)/n,1);
for(ans[b[x]]=n-1;ans[b[x]]>=0;ans[b[x]]--)
if(!used[ans[b[x]]])
{
used[ans[b[x]]]=1;
if(ok(x,jinwei) && dfs(x-1,(ans[a[x]]+ans[b[x]]+jinwei)/n,1))return 1;
while(q[0]>p)
{
k=q[q[0]],used[ans[k]]=0;
ans[k]=-1,q[0]--;
}
used[ans[b[x]]]=0;
}
return 0;
}
int main()
{
int i;
scanf("%d%s%s%s",&n,a,b,c);
for(i=0;i<n;i++)ans['A'+i]=-1;
dfs(n-1,0,1);
printf("%d",ans['A']);
for(i=1;i<n;i++)printf(" %d",ans['A'+i]);
return 0;
}
相关文章推荐
- Bootstrap Modal 加滚动条
- 一幅图弄清DFT与DTFT,DFS的关系【转载】
- myeclipse中web项目没有run on server的解决办法
- Android安全(MDM、Root、Hook、Inject等)、软件加固、移动保护
- 网易校园招聘java面试经历
- Designing JSP Custom Tag Libraries
- 因为断点无法进入 使用记事本另存为unicode编码文件,导致的link2001错误 解决方法
- ASP.NET 防盗链的实现[HttpHandler]
- C#实现 word、pdf、ppt 转为图片
- 服务没有mysql
- qq群文件管理
- java代理模式的实现方法
- C#:通过Window API接口实现WiFi
- 数据库空值和null的区别
- Spring笔记――2.使用Spring容器
- 字符串转数字,包括正数,负数,小数
- 流程控制语句和增强for循环
- 重定向
- 搭建git for windows服务器(100%可以成功)
- VIM设置搜索字符高亮