题意描述:
【问题描述】
这个题目要求你编写一个程序来解决一个纵横填字游戏。
这个游戏比我们在报纸上见到的通常的填字游戏要简单。游戏仅给出单词的起始位置,方面(横向或纵向)以及单词的长度。只要单词的长度正好,游戏中能填入任何一个来自词典的单词。
在游戏中单词相交处的字母必须相同,当然,任何单词只准使用一次。
思考一下以下这个游戏。
例如,假定从上到下有5行,用0到4来表示,从左到右有5列,用0到4来表示。我们用(X, Y)来表示填字游戏中第X列和第Y行的字母。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
在这个游戏中,我们需填入5个单词:在(0, 0)的右边填入一个4个字母的单词,在(0, 0)的下方填入一个4个字母的单词,在(2, 0)的下方填入一个4个字母的单词,在(0, 2)的右边填入一个3个字母的单词,最后在(2, 3)的右边填入一个3个字母的单词。字典上所有的单词都能使用但最多只能使用一次。例如,以下是一个可能的解决方案。
(0, 0)右边,LATE
(0, 0)下面,LIED
(2, 0)下面,TELL
(2, 3)右边,LOW
【输入】
输入文件的第一行是作为字典使用的—个文本文件名,你可以假定这个文件存在,是可读的并且包含最多不超过100000个单词,并且按词典顺序存储,每行一个单词。字典中所有的单词所含的字母可以是大写或小写(在这个问题中字母的大小写是无关紧要的)。你可以假设字典中所有单词的长度不超过20个字符。输入文件的下一行包含一个整数n(n≤15),表示要填的单词的数量。接下来的n行中每行给出关于一个单词的提示,在每个提示中分别给出单词的首字母在填字游戏中的列和行的位置,后面根据单词的方向是横向还是纵向,相应跟字符A或字符D,最后一个数表示该单词的长度,以上数据之间均用空格隔开。
你能作以下的进一步的假设。
(1)填字游戏的尺寸不超过10×10。
(2)游戏盘中放得下所有的单词。
(3)用给定的词典是能够破解出该游戏的。
【输出】
输出文件应该包含n行,输出游戏中可填入的所有单词。单词应该每行出现一个,并且按输入文件中提示的顺序输出。每个单词中所有的字母必须是大写的。所有的单词必须来自给定的词典文件(忽略大小写)。任何单词只能使用一次。对于给定输入文件可能有大量的正确解决方案,你只须输出其中的任意一个解决方案。
【样例】
puzzle.in puzzle.out
words.txt LATE
5 LIED
00 A 4 TELL
00 D 4 EEL
20 D 4 LOW
02 A 3
23 A 3
解题思路:
真的不会做这道题,所以看了老李的题解,发现实在太神了,写了很久才才对,又发现漏了一个优化。
这真的是一道很神的DFS,主要剪枝来自两个方向:
1、Hash出当前可以插入的所有字串:在处理当前串的时候,这个串中会有一些格子是已经被填过的,有一些格子是没被填过的,利用这个特性设计一个Hash函数使得被填过格子相同的待填字串都在一个桶里,预处理出每一个待填字串在填每一个串时的Hash值即可。
2、调整搜索顺序,使得被搜的前几个字串之中有尽可能多的重叠,以减小搜索树的形态。
#include<iostream> #include<cstdlib> using namespace std; #include<cstdio> #include<cstring> #include<ctime> #include<algorithm> const int P=1000007; char c[15]; int num[11],n,x[15],y[15],len[15],a[10][10]; bool flag[11][100000],p[15][10]; struct S{ S * next; int len,k; }hash[15][P]; struct DS{ int x,y; }b[2]={(DS){1,0},(DS){0,1}}; struct SS{ char s[12]; int i; inline bool operator < (SS a) const{ return strcmp(s,a.s)<0; } }s[11][100000],ans[15]; inline void dfs(int I){ if(I==n){ int tot=0; for(int i=1;i<11;++i) for(int j=0;j<num[i];++j) if(flag[i][j]) ans[tot++]=s[i][j]; while(--tot+1){ for(int j=0;j<strlen(ans[tot].s);++j) putchar(ans[tot].s[j]+'A'-1); putchar('\n'); } exit(0); } int X,Y,k,d,now,i,l; now=0; for(X=x[I],Y=y[I],d=0;d<len[I];++d,X+=b[c[I]].x,Y+=b[c[I]].y) if(p[I][d]) now=(now*27+a[X][Y])%P; for(S * j=hash[I][now].next;j;j=j->next){ l=j->len,k=j->k; if(flag[l][k])continue; for(X=x[I],Y=y[I],d=0;d<len[I];++d,X+=b[c[I]].x,Y+=b[c[I]].y)a[X][Y]=s[l][k].s[d]; flag[l][k]=1; dfs(I+1); flag[l][k]=0; } } int main(){ /*---------Read---------*/ FILE * fin=fopen("puzzle.in","r"); freopen("puzzle.out","w",stdout); char name[100],stmp[30]; int i,tot=0,l; fscanf(fin,"%s",name); FILE * fn=fopen(name,"r"); while(~fscanf(fn,"%s",stmp)) if((l=strlen(stmp))<11){ for(i=0;i<l;++i)stmp[i]=toupper(stmp[i])-'A'+1; memcpy(s[l][num[l]].s,stmp,l); s[l][num[l]++].i=tot++; } fscanf(fin,"%d",&n); for(i=0;i<n;++i)fscanf(fin,"%d%d%*c%c%d",y+i,x+i,c+i,len+i),c[i]=c[i]=='A'?1:0; /*-------Select-------*/ int X,Y,k,d,now,sum,MAX,maxi; bool flag[10][10]={0}; for(k=0;k<n;++k){ MAX=-1; for(i=k;i<n;++i){ sum=0; for(X=x[i],Y=y[i],d=0;d<len[i];++d,X+=b[c[i]].x,Y+=b[c[i]].y) if(flag[X][Y]) sum++; if(sum>MAX)MAX=sum,maxi=i; } swap(x[maxi],x[k]),swap(y[maxi],y[k]),swap(c[maxi],c[k]),swap(len[maxi],len[k]); for(X=x[k],Y=y[k],d=0;d<len[k];++d,X+=b[c[k]].x,Y+=b[c[k]].y)flag[X][Y]=1; } /*-------Prework-----*/ int j; for(i=0;i<n;++i) for(j=0;j<n;++j) hash[i][j].next=NULL; S * tmp; memset(flag,0,sizeof(flag)); for(i=0;i<n;++i){ for(X=x[i],Y=y[i],d=0;d<len[i];++d,X+=b[c[i]].x,Y+=b[c[i]].y) if(flag[X][Y]) p[i][d]=1; for(k=0;k<num[len[i]];++k){ now=0; for(d=0;d<len[i];++d) if(p[i][d]) now=(now*27+s[len[i]][k].s[d])%P; tmp=new S; tmp->next=hash[i][now].next; tmp->len=len[i]; tmp->k=k; hash[i][now].next=tmp; } for(X=x[i],Y=y[i],j=0;j<len[i];++j,X+=b[c[i]].x,Y+=b[c[i]].y)flag[X][Y]=1; } /*------DFS------*/ dfs(0); }
①对于字符串的题,优先考虑Hash。
②对于可行性搜索,一定要调整搜索顺序,绝对可以增速,要尽量使强剪枝位于搜索树上部的位置。
③处理字符串时一定要头脑清晰,我做这道题花了5个小时的时间,被大量繁杂的循环处理弄昏了头脑。