1. 程式人生 > >[POJ 1204]Word Puzzles(Trie樹暴搜&AC自己主動機)

[POJ 1204]Word Puzzles(Trie樹暴搜&AC自己主動機)

cloc produce cte owin you dsm queue pos cti

Description

Word puzzles are usually simple and very entertaining for all ages. They are so entertaining that Pizza-Hut company started using table covers with word puzzles printed on them, possibly with the intent to minimise their client‘s perception of any possible delay in bringing them their order.

Even though word puzzles may be entertaining to solve by hand, they may become boring when they get very large. Computers do not yet get bored in solving tasks, therefore we thought you could devise a program to speedup (hopefully!) solution finding in such puzzles.

The following figure illustrates the PizzaHut puzzle. The names of the pizzas to be found in the puzzle are: MARGARITA, ALEMA, BARBECUE, TROPICAL, SUPREMA, LOUISIANA, CHEESEHAM, EUROPA, HAVAIANA, CAMPONESA.
技術分享

Your task is to produce a program that given the word puzzle and words to be found in the puzzle, determines, for each word, the position of the first letter and its orientation in the puzzle.

You can assume that the left upper corner of the puzzle is the origin, (0,0). Furthemore, the orientation of the word is marked clockwise starting with letter A for north (note: there are 8 possible directions in total).

Input

The first line of input consists of three positive numbers, the number of lines, 0 < L <= 1000, the number of columns, 0 < C <= 1000, and the number of words to be found, 0 < W <= 1000. The following L input lines, each one of size C characters, contain the word puzzle. Then at last the W words are input one per line.

Output

Your program should output, for each word (using the same order as the words were input) a triplet defining the coordinates, line and column, where the first letter of the word appears, followed by a letter indicating the orientation of the word according to the rules define above. Each value in the triplet must be separated by one space only.

Sample Input

20 20 10
QWSPILAATIRAGRAMYKEI
AGTRCLQAXLPOIJLFVBUQ
TQTKAZXVMRWALEMAPKCW
LIEACNKAZXKPOTPIZCEO
FGKLSTCBTROPICALBLBC
JEWHJEEWSMLPOEKORORA
LUPQWRNJOAAGJKMUSJAE
KRQEIOLOAOQPRTVILCBZ
QOPUCAJSPPOUTMTSLPSF
LPOUYTRFGMMLKIUISXSW
WAHCPOIYTGAKLMNAHBVA
EIAKHPLBGSMCLOGNGJML
LDTIKENVCSWQAZUAOEAL
HOPLPGEJKMNUTIIORMNC
LOIUFTGSQACAXMOPBEIO
QOASDHOPEPNBUYUYOBXB
IONIAELOJHSWASMOUTRK
HPOIYTJPLNAQWDRIBITG
LPOINUYMRTEMPTMLMNBO
PAFCOPLHAVAIANALBPFS
MARGARITA
ALEMA
BARBECUE
TROPICAL
SUPREMA
LOUISIANA
CHEESEHAM
EUROPA
HAVAIANA
CAMPONESA

Sample Output

0 15 G
2 11 C
7 18 A
4 8 C
16 13 B
4 15 E
10 3 D
5 1 E
19 7 C
11 11 H

Source

field=source&key=Southwestern+Europe+2002" target="_blank">Southwestern Europe 2002

1、Trie樹暴搜,能夠說是Trie模板題。將單詞插入Trie樹後,以地圖上每一個點作為起點,8個方向搜索單詞就可以,WA了6發,總算AC了。須要註意的就是當眼下的單詞已經搜到盡頭(終止節點),不要停止搜索!

#include <stdio.h>
#include <iostream>
#include <string>
#include <string.h>

#define WORD 26
#define MAXN 1050

using namespace std;

struct trie
{
	trie *child[WORD]; //兒子節點指針
	int id; //若該節點為某單詞的終止節點,id=該單詞編號
	trie(){
		memset(child,0,sizeof(child)); //兒子節點初始化為空
		id=-1;
	}
}; //root=根節點

trie *root=new trie();

int dx[]={-1,-1,0,1,1,1,0,-1};
int dy[]={0,1,1,1,0,-1,-1,-1}; //搜索方向,暴力枚舉每一個點八個方向
int ans[MAXN][3],visit[MAXN],L,C,W; //第i個單詞的位置=(ans[i][1],ans[i][2]),方向為ans[i][0]

string c[MAXN],word; //保存全部字符串的矩陣

void build(string s,int num) //將編號為num的字符串s插入trie樹中
{
	int i;
	trie *p=root; //初始時p指向根節點
	for(i=0;i<s.size();i++)
	{
		if(p->child[s[i]-'A']==NULL) //無現成的字符串相應位置兒子節點
			p->child[s[i]-'A']=new trie();
		p=p->child[s[i]-'A']; //將指針移向當前節點以下的相應兒子節點
	}
	p->id=num; //記下終止節點的相應單詞編號
}

void search(int sx,int sy,int dir) //搜索trie樹,單詞起點(sx,sy),延伸方向為dir
{
	int xx=sx,yy=sy; //當前單詞字母的坐標(xx,yy)
	trie *point=root; //point指向當前trie樹的節點
	while(xx>=0&&xx<L&&yy>=0&&yy<C) //坐標未越界
	{
		if(!point->child[c[xx][yy]-'A']) //到達了終止節點,但單詞還沒結束
			break;
		else
			point=point->child[c[xx][yy]-'A']; //指針向該節點下方移動
		if(point->id!=-1) //該節點為終止節點
		{
			if(visit[point->id]==0)
			{
				visit[point->id]=1;
				ans[point->id][0]=dir; //記錄下該單詞的開始坐標、方向
				ans[point->id][1]=sx;
				ans[point->id][2]=sy;
			}
		}
		xx+=dx[dir]; //移動坐標
		yy+=dy[dir];
	}
}

int main()
{
	int i,j,k;
	scanf("%d%d%d",&L,&C,&W);
	for(i=0;i<L;i++)
	{
		cin>>c[i];
	}
	for(i=0;i<W;i++)
	{
		cin>>word;
		build(word,i); //將單詞插入trie樹
	}
	for(i=0;i<L;i++) //枚舉起點(i,j)
		for(j=0;j<C;j++)
			for(k=0;k<8;k++) //暴力枚舉單詞存在的方向
				search(i,j,k);
	for(i=0;i<W;i++)
		printf("%d %d %c\n",ans[i][1],ans[i][2],ans[i][0]+'A');
	return 0;
}

2、AC自己主動機。Trie樹暴力的方法由於是從地圖上每一個點開始搜,並且Trie樹失配後就必須回到根節點又一次來過,所以復雜度太高。能夠直接用AC自己主動機,利用AC自己主動機能夠將字符串的後綴和模式串匹配的特點,從地圖的四個邊上的點作為起點,向八個方向匹配就可以。加之匹配過程中失配後能夠沿著失敗指針向上走,不必直接到根節點重頭來,因此復雜度比第一種方法低不少,可是代碼也太長太復雜(140行)

#include <stdio.h>
#include <iostream>
#include <queue>
#include <string>
#include <string.h>

#define WORD 26
#define MAXN 1050

using namespace std;

struct trie
{
	trie *child[WORD]; //兒子節點指針
	trie *fail; //失敗指針(前綴指針)
	int id; //若該節點為某單詞的終止節點,id=該單詞編號
	trie()
	{
		memset(child,0,sizeof(child)); //兒子節點初始化為空
		fail=NULL;
		id=-1;
	}
}tree[MAXN*100]; //root=根節點

int dx[]={-1,-1,0,1,1,1,0,-1};
int dy[]={0,1,1,1,0,-1,-1,-1}; //搜索方向,暴力枚舉每一個點八個方向
int ans[MAXN][3],len[MAXN],L,C,W,nNodesCount=0; //第i個單詞的位置=(ans[i][1],ans[i][2]),方向為ans[i][0],nNodeCounts=節點個數

string c[MAXN],word; //保存全部字符串的矩陣

void build(trie *pRoot,string s,int num) //將編號為num的字符串s插入樹根為pRoot的trie樹中
{
	int i;
	len[num]=s.size();
	for(i=0;s[i];i++)
	{
		if(pRoot->child[s[i]-'A']==NULL) //無現成的字符串相應位置兒子節點
		{
			pRoot->child[s[i]-'A']=tree+nNodesCount;
			nNodesCount++; //節點個數+1
		}
		pRoot=pRoot->child[s[i]-'A']; //將指針移向當前節點以下的相應兒子節點
	}
	pRoot->id=num; //記下終止節點的相應單詞編號
}

void acAutomation() //搭建前綴指針。構造AC自己主動機
{
	int i;
	for(i=0;i<WORD;i++)
		tree[0].child[i]=tree+1; //讓第0個節點的全部兒子節點都指向第一個節點,即不論什麽單詞都能從根節點匹配下去
	tree[0].fail=NULL; //根節點的失敗指針指向空
	tree[1].fail=tree; //第一個節點的失敗指針指向根節點
	trie *pRoot,*point;
	queue<trie*>q;
	q.push(tree+1); //將第一個節點入隊
	while(!q.empty()) //隊列不為空
	{
		pRoot=q.front();
		q.pop(); //隊首出隊
		for(i=0;i<WORD;i++) //遍歷該節點的兒子
		{
			point=pRoot->child[i]; //point=單詞當前字母相應的兒子
			if(point)
			{
				trie *pPrev=pRoot->fail; //失敗指針指向的節點
				while(pPrev) //當指向的節點不為空,不斷向上爬
				{
					if(pPrev->child[i]) //指向的節點能匹配
					{
						point->fail=pPrev->child[i]; //將該節點的失敗指針指向失敗指針指向的節點的能夠匹配的兒子節點
						break;
					}
					else
						pPrev=pPrev->fail; //無法匹配,向該失敗指針指向的節點的失敗指針往上爬
				}
				q.push(point); //該節點入隊
			}
		}
	}
}

void ACsearch(int sx,int sy,int dir) //搜索,單詞起點(sx,sy),延伸方向為dir
{
	int i,xx=sx,yy=sy;
	trie *point=tree+1;
	while(xx>=0&&xx<L&&yy>=0&&yy<C)
	{
		while(1)
		{
			if(point->child[c[xx][yy]-'A']) //該節點的兒子節點能夠匹配
			{
				point=point->child[c[xx][yy]-'A']; //指針向兒子移動
				if(point->id!=-1) //指針相應節點為危急節點,則匹配成功,記錄答案
				{
					ans[point->id][0]=dir; //記錄下該單詞的開始坐標、方向
					ans[point->id][1]=xx-(len[point->id]-1)*dx[dir];
					ans[point->id][2]=yy-(len[point->id]-1)*dy[dir];
				}
				break;
			}
			else
				point=point->fail; //否則,無法匹配,向失敗指針移動
		}
		xx+=dx[dir];
		yy+=dy[dir];
	}
}

int main()
{
	int i,j,k;
	nNodesCount=2;
	scanf("%d%d%d",&L,&C,&W);
	for(i=0;i<L;i++) cin>>c[i];
	for(i=0;i<W;i++)
	{
		cin>>word;
		build(tree+1,word,i); //將單詞插入trie樹
	}
	acAutomation(); //插入失敗指針,構造AC自己主動機
	for(i=0;i<L;i++)
		for(j=0;j<8;j++)
			ACsearch(i,0,j);
	for(i=0;i<L;i++)
		for(j=0;j<8;j++)
			ACsearch(i,C-1,j);
	for(i=0;i<C;i++)
		for(j=0;j<8;j++)
			ACsearch(0,i,j);
	for(i=0;i<C;i++)
		for(j=0;j<8;j++)
			ACsearch(L-1,i,j);
	for(i=0;i<W;i++)
		printf("%d %d %c\n",ans[i][1],ans[i][2],ans[i][0]+'A');
	return 0;
}


[POJ 1204]Word Puzzles(Trie樹暴搜&amp;AC自己主動機)