[bzoj3277][bzoj3473][SAM]串

8 篇文章 0 订阅

Description

字符串是oi界常考的问题。现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中
至少k个字符串的子串(注意包括本身)。

Input

第一行两个整数n,k。 接下来n行每行一个字符串。 n,k,l<=100000

Output

输出一行n个整数,第i个整数表示第i个字符串的答案。

Sample Input

3 1

abc

a

ab

Sample Output

6 1 3

题解

怎么连SAM基本玩法都不会了呀…
先预处理出每个状态点在多少个字符串中
设mark[i]表示第i个状态最后一次被找到是在哪个字符串中 ct[i]表示这个状态出现在多少字符串中
每次插入的时候暴力跳parent
如果mark[parent]!=当前串 则把mark[parent]赋值为当前串并且ct++
每个状态点表示的本质不同的字符串有dep[i]-dep[parent[i]]个
如果这个状态点的ct>=K 则它的权值为dep[i]-dep[parent[i]] 否则为0
这样求出来的是不重复的 但是题目要求是可以重复
所以还要加上他的parent树所有祖先的权值
之后扫串更新答案
在建新节点的时候记得记得记得拷他的mark和ct…

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<vector>
#define LL long long
using namespace std;
vector<int> hh[110000];
struct node{int x,y,next;}a[810000];int len,last[410000];
void ins(int x,int y){len++;a[len].x=x;a[len].y=y;a[len].next=last[x];last[x]=len;}
struct SAM
{
	int son[30],dep,parent;
}tr[410000];int root,cnt,LA,T;
int ct[410000],mark[410000];//这个节点是否被标记过
char ch[410000];
void add(int x)
{
	int np=++cnt,p=LA;
	tr[np].dep=tr[p].dep+1;
	while(p&&!tr[p].son[x])tr[p].son[x]=np,p=tr[p].parent;
	if(!p)tr[np].parent=root;
	else
	{
		int q=tr[p].son[x];
		if(tr[q].dep==tr[p].dep+1)tr[np].parent=q;
		else
		{
			int nq=++cnt;
			tr[nq]=tr[q];tr[nq].dep=tr[p].dep+1;ct[nq]=ct[q];mark[nq]=mark[q];
			tr[q].parent=tr[np].parent=nq;
			while(p&&tr[p].son[x]==q)tr[p].son[x]=nq,p=tr[p].parent;
		}
	}
	LA=np;
	while(np&&mark[np]!=T)ct[np]++,mark[np]=T,np=tr[np].parent;
}
LL f[210000];
void dfs(int x)
{
	f[x]+=f[tr[x].parent];
	for(int k=last[x];k;k=a[k].next)dfs(a[k].y);
}
int n,K;
int main()
{
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
	scanf("%d%d",&n,&K);
	root=LA=++cnt;
	for(int i=1;i<=n;i++)
	{
		scanf("%s",ch+1);
		int ln=strlen(ch+1);LA=root;T++;
		for(int j=1;j<=ln;j++)add(ch[j]-'a'),hh[i].push_back(ch[j]-'a');
	}
	for(int i=1;i<=cnt;i++)ins(tr[i].parent,i),f[i]=((ct[i]>=K)?(tr[i].dep-tr[tr[i].parent].dep):(0));
   // for(int i=1;i<=cnt;i++)printf("%d ",ct[i]);
	dfs(1);
	for(int i=1;i<=n;i++)
	{
		LL ans=0;
		for(int p=root,j=0;j<hh[i].size();j++)ans+=f[p=tr[p].son[hh[i][j]]];
		printf("%lld ",ans);
	}
	puts("");
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值