[WC 2015复习](二)与字符串有关的算法及数据结构

都是比较简单SB的东西,求各位去WC的神犇勿喷。

1、Trie

(1)[BZOJ 1212][HNOI 2004]L语言

http://www.lydsy.com/JudgeOnline/problem.php?id=1212

不妨设f[i]=true表明当前的文章的前i个字符是合法前缀,那么很容易想到f[i]=true可以推出f[j]=true,其中i+1~j部分是一个单词。

很容易想到把每个单词都放进Trie里面,从0~文章长度遍历f[i],若f[i]=true,把文章从i+1开始的部分放入Trie中匹配,并得到新的f[j]=true的部分,最终我们遍历f数组便可找出最长合法前缀。

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

#define MAXN 1<<20|100
#define MAXM 23
#define NUM(x) ((x)-'a') //字符x对应0~25中的编号

using namespace std;

struct Trie
{
	struct Node
	{
		int son[26],fa; //儿子指针、父结点指针
		bool isBadNode; //危险结点(单词结尾)标记
	}node[MAXN];
	
	int nCount;

	void Insert(char *s) //插入字符串s
	{
		int p=0,len=strlen(s); //p是当前遍历到的Trie的结点,len是字符串s的长度
		for(int i=0;i<len;i++)
		{
			if(!node[p].son[NUM(s[i])])
				node[p].son[NUM(s[i])]=++nCount;
			p=node[p].son[NUM(s[i])];
		}
		node[p].isBadNode=true;
	}
}trie;

int n,m;
char str[MAXN],word[MAXM]; //待匹配的文章、要插入的单词
bool f[MAXN];

void ask(char *s,int i) //当前的字符串s是从原来的字符串的下标i处开始的
{
	int p=0; //遍历到的Trie树的结点编号
	int t=0; //已经遍历到的字符串长度
	while(*s!='\0')
	{
		if(!trie.node[p].son[NUM(*s)]) return; //无法完全匹配
		p=trie.node[p].son[NUM(*s)];
		s++; //字符串的指针向后移动一格
		t++; //标记成功匹配上的字符串长度+1
		if(trie.node[p].isBadNode) f[i+t]=true; //成功匹配上了一个单词
	}
	if(trie.node[p].isBadNode) f[i+t]=true;
}

int work() //求当前文章str最长合法前缀
{
	int maxans; //最长合法前缀
	memset(f,false,sizeof(f));
	f[0]=true;
	int len=strlen(str+1);
	for(int i=0;i<=len;i++)
	{
		if(!f[i]) continue;
		maxans=i;
		ask(str+i+1,i);
	}
	return maxans;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",word);
		trie.Insert(word);
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%s",str+1);
		printf("%d\n",work());
	}
	return 0;
}


2、AC自动机

(1)[BZOJ 3172][Tjoi 2013]单词

http://www.lydsy.com/JudgeOnline/problem.php?id=3172

首先将所有单词都插入AC自动机的Trie树中,记录下每个单词的危险节点(最后一个字符对应的节点)在Trie树中的编号。可以把Trie树中每个结点的fail指针看成一条边,fail指针指向的节点看成新树中这个节点的父亲,这就构成了fail树。我们记录下每个结点i在插入单词过程中被访问的次数f[i],那么在fail树中,对于每个点u及其儿子v而言,f[u]=f[u]+f[vi],vi是u的儿子(u代表文章的一个前缀,其fail指针所指向的点代表这个前缀的一个后缀,显然这个后缀的出现次数要算上它所在的所有前缀的出现次数)。那么单词i的出现次数就是i的危险节点的f值。

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

#define MAXN 1000010
#define MAXM 1000010
#define NUM(x) ((x)-'a')

using namespace std;

int pointer[210]; //pointer[i]=编号为i的模式串的危险节点的编号

struct AC_Automation
{
	struct Trie //带fail指针的Trie树
	{
		struct Node //Trie树结点
		{
			int son[26]; //儿子指针
			int fail; //失败指针
			int cnt; //该结点在插入单词时的被匹配次数
		}node[MAXN];

		int nCount;

		void Insert(char *s,int num) //将编号为num的模式串插入到Trie中
		{
			int p=0;
			while(*s!='\0')
			{
				if(!node[p].son[NUM(*s)])
					node[p].son[NUM(*s)]=++nCount;
				p=node[p].son[NUM(*s)];
				s++;
				node[p].cnt++;
			}
			pointer[num]=p;
		}
	}trie; //AC自动机用的Trie树
	
	void build(int cnt) //AC自动机的初始插入cnt个模式串操作、以及构建fail指针
	{
		char s[MAXM];
		for(int i=1;i<=cnt;i++)
		{
			scanf("%s",s);
			trie.Insert(s,i); //在Trie树中插入第i个模式串
		}
		//广搜建立失配指针
		int q[MAXM],h=0,t=0; //广搜用的队列
		for(int i=0;i<26;i++)
			if(trie.node[0].son[i])
			{
				trie.node[trie.node[0].son[i]].fail=0;
				q[t++]=trie.node[0].son[i];
			}
		while(h<t)
		{
			int now=q[h++];
			for(int i=0;i<26;i++)
			{
				if(trie.node[now].son[i])
				{
					int tmp=trie.node[now].fail;
					while(tmp!=0&&trie.node[tmp].son[i]==0)
						tmp=trie.node[tmp].fail;
					if(trie.node[tmp].son[i])
						tmp=trie.node[tmp].son[i];
					trie.node[trie.node[now].son[i]].fail=tmp;
					q[t++]=trie.node[now].son[i];
				}
			}
		}
		while(t)
		{
			t--;
			trie.node[trie.node[q[t]].fail].cnt+=trie.node[q[t]].cnt;
		}
	}
}acAutomation;

int main()
{
	int n;
	scanf("%d",&n);
	acAutomation.build(n);
	for(int i=1;i<=n;i++)
		printf("%d\n",acAutomation.trie.node[pointer[i]].cnt);
	return 0;
}


3、KMP

(1)[POJ 3461]Oulipo

http://poj.org/problem?id=3461

题目大意:给出模式串W和主串T,求W在T中的出现次数。

最基础的KMP。求出next后在主串中匹配即可,注意匹配成功后,模式串中的指针k要从最末尾处退到next[k]处,准备下一次匹配。

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

#define MAXN 1000100
#define MAXM 10010
using namespace std;

char word[MAXM],text[MAXN]; //子串与待匹配文章
int next[MAXM];

void getNext(char str[],int len) //求长度为len的模式串str的next指针
{
	int k=0;
	next[1]=0;
	for(int i=2;i<=len;i++)
	{
		while(k>0&&str[k+1]!=str[i]) k=next[k];
		if(str[k+1]==str[i]) k++;
		next[i]=k;
	}
}

int match(char W[],int lenW,char T[],int lenT) //将模式串W匹配主串T,求W在T中的出现次数
{
	int ans=0;
	getNext(W,lenW);
	int k=0; //k=W已经匹配了的部分的长度
	for(int i=1;i<=lenT;i++)
	{
		while(k>0&&W[k+1]!=T[i]) k=next[k];
		if(W[k+1]==T[i]) k++;
		if(k==lenW)
		{
			ans++;
			k=next[k];
		}
	}
	return ans;
}

int main()
{
	int TestCase;
	scanf("%d",&TestCase);
	while(TestCase--)
	{
		memset(next,0,sizeof(next));
		scanf("%s+1",word+1);
		scanf("%s+1",text+1);
		int lenw=strlen(word+1);
		int lent=strlen(text+1);
		printf("%d\n",match(word,lenw,text,lent));
	}
	return 0;
}


4、后缀数组

(1)[BZOJ 3224]Tyvj 1728 普通平衡树

http://www.lydsy.com/JudgeOnline/problem.php?id=3224

基础的平衡树操作。对序列中单点修改、求第k小数、求某个数的排名、查询某个数的前驱后继。



你可能感兴趣的:([WC 2015复习](二)与字符串有关的算法及数据结构)