bzoj-3172 单词

141 篇文章 0 订阅
75 篇文章 0 订阅

题意:

给出n个单词,求在这n个单词组成的文章中每个单词出现了多少次;

n<=200,n个字符串总长度大概是500000  (数据范围显然有误);


题解:

一开始考虑就是先建个自动机,然后对每个串匹配一次;

经过的所有节点 和后缀的cnt全部+1;

然后查询每个单词的危险结点的cnt值就是答案;

复杂度O(n*len+k*n*len)基本也是线性的,似乎可过;

结果我T了;

然后查了一下题解,有种更好的思路,不需要用串去在自动机上匹配;

就是利用fail指针反向来构建一颗fail树;

首先因为除了根结点每个结点都有fail指针所以是n-1条边;

而显然所以fail指针最后都会回到root,即反向后的fail树中root可以到达所有点;

n点n-1边并且可以从一个点到达所有,这就是一颗有向树了;

根据fail指针的特性,倘若从树上某点x向根走,经过的每个点所代表的字符串都是x的后缀;

而一个字符串在另一个中的条件可以说成是,这个字符串是另一个字符串的某前缀的后缀;

那么可以想到从一个串的所有前缀向上拓展,每个能经过的点的cnt都增加;

这似乎是个动态规划的样子,具体实现就是:

建立自动机时将路径每个点的cnt都+1   (作为这个字符串的前缀);

对每个fail建立一个反向边;

根据反向边做树形dp,姿势和求子树权值和是一样的;

cnt[x]=∑cnt[ son[x] ] +cnt[x],得到的cnt就是答案;

其实这步也可以记录宽搜时的BFS序,反过来枚举就是满足的;

然后!我交上去居然还是T!

然后!。。我发现我模板少敲一句话;

好了,解决之后上面的两种方法都是可以A的,只是时间不同   (分明就是卡时啊);

10365141401423172Accepted158720 kb10940 ms1456 B2015-07-07 21:34:16
10365111401423172Accepted160680 kb252 ms1235 B2015-07-07 21:33:15
就是如此了,手一滑的危机啊;


代码:


正解:

#include<queue>
#include<vector>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 510000
using namespace std;
char a[201][N];
int next[N][27],fail[N],cnt[N],ans[201],q[N];
int st,en,root=1,tot=1;
int insert(char *s)
{
	int index,p;
	p=root;
	while(*s!='\0')
	{
		index=*s-'a';
		if(!next[p][index])
			next[p][index]=++tot;
		p=next[p][index];
		*s++;
		cnt[p]++;
	}
	return p;
}
void Build()
{
	int i,p,temp;
	q[st=en=1]=root;
	while(st<=en)
	{
		p=q[st++];
		for(i=0;i<26;i++)
		{
			if(next[p][i])
			{
				temp=fail[p];
				while(temp)
				{
					if(next[temp][i])
					{
						fail[next[p][i]]=next[temp][i];
						break;
					}
					temp=fail[temp];
				}
				if(!temp)	fail[next[p][i]]=root;
				q[++en]=next[p][i];
			}
		}
	}
}
void slove()
{
	while(en)
		cnt[fail[q[en]]]+=cnt[q[en--]];
}
int main()
{
	int n,i;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%s",a[i]);
		ans[i]=insert(a[i]);
	}
	Build();
	slove();
	for(i=1;i<=n;i++)
		printf("%d\n",cnt[ans[i]]);
	return 0;
}

歪解:

#include<queue>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 510000
using namespace std;
queue<int>q;
char a[201][N];
int next[N][27],fail[N],cnt[N],ans[201],root=1,tot=1;
int insert(char *s)
{
	int index,p;
	p=root;
	while(*s!='\0')
	{
		index=*s-'a';
		if(!next[p][index])
			next[p][index]=++tot;
		p=next[p][index];
		*s++;
	}
	return p;
}
void Build()
{
	int i,p,temp;
	q.push(root);
	while(!q.empty())
	{
		p=q.front(),q.pop();
		for(i=0;i<26;i++)
		{
			if(next[p][i])
			{
				temp=fail[p];
				while(temp)
				{
					if(next[temp][i])
					{
						fail[next[p][i]]=next[temp][i];
						break;
					}
					temp=fail[temp];
				}
				if(!temp)	fail[next[p][i]]=root;
				q.push(next[p][i]);
			}
		}
	}
}
void query(char *s)
{
	int index,p,temp;
	p=root;
	while(*s!='\0')
	{
		index=*s-'a';
		while(!next[p][index]&&!p)
			p=fail[p];
		p=p?next[p][index]:root;
		temp=p;
		while(temp)
		{
			cnt[temp]++;
			temp=fail[temp];
		}
		s++;
	}
}
int main()
{
	int n,i;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%s",a[i]);
		ans[i]=insert(a[i]);
	}
	Build();
	for(i=1;i<=n;i++)
		query(a[i]);
	for(i=1;i<=n;i++)
		printf("%d\n",cnt[ans[i]]);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值