http://www.lydsy.com/JudgeOnline/problem.php?id=2754
来自出题人满满的恶意。。。。
有两种做法:1、AC自动机 2、后缀数组。
AC自动机做法很复杂,因为此题非常丧病地没有限定字符集的大小,这样就导致不能用Trie树传统的保存儿子的方式,只能用map,并且这样会让最终的算法复杂度多一个 log 。
而后缀数组的做法就随意了很多,因为SA对字符集没有什么特别的要求,字符串都是可以看成是数字串,非常爽,果断用SA。
SA的做法看上去就是乱搞:首先把所有猫的名的字符串、猫的姓的字符串、点名的字符串拼成一个大的字符串,每个不同的串之间用不同的分割符隔开,注意要不同的分割符,为了避免RP爆零时匹配错误。
然后注意到题目的一个很重要的限制:一个猫被点名,点名串可以是它的姓的子串,也可是它的名的子串,但是绝对不能是它的姓和名拼起来的子串,因此每个猫的姓和名必须分开看待。
另外要注意到SA一个非常关键的性质:对于一些具有相同前缀的后缀来说,他们的排名是挨在一起的,对于一个开头下标为 t ,长为 len 的子串而言,包含它的后缀与开头下标为 t 的后缀的LCP值一定是大于等于 len 的,据此,对于每个点名串 i ,我们可以暴力在 height 数组中扫一遍找出一个区间 [L,R] ,以这个区间中的数字 i 的 sa[i] 作为开头下标的后缀都是包含第 i 个点名串的,然后在 [L,R] 中扫一遍,通过 sa 数组看有哪些猫的名或姓在这其中,统计答案即可。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 1000000
using namespace std;
int sa[MAXN],height[MAXN],rank[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],cnt[MAXN];
bool cmp(int *r,int a,int b,int c) //第一关键字a与b,第二关键字a+c,b+c
{
return (r[a]==r[b])&&(r[a+c]==r[b+c]);
}
void SA(int *r,int n,int m) //待排序串为r,长度n-1,共有m个不同的字母
{
int i,j,p;
int *x=wa,*y=wb;
for(int i=0;i<m;i++) cnt[i]=0;
for(int i=0;i<n;i++) cnt[x[i]=r[i]]++;
for(int i=1;i<m;i++) cnt[i]+=cnt[i-1];
for(int i=n-1;i>=0;i--) sa[--cnt[x[i]]]=i;
for(j=1,p=1;p<n;j*=2,m=p)
{
for(p=0,i=n-j;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0;i<n;i++) wv[i]=x[y[i]]; //现在wv中放的是按照第二关键字排序后的二元组的第一关键字数组
for(i=0;i<m;i++) cnt[i]=0; //清空桶
for(i=0;i<n;i++) cnt[wv[i]]++;
for(i=1;i<m;i++) cnt[i]+=cnt[i-1];
for(i=n-1;i>=0;i--) sa[--cnt[wv[i]]]=y[i];
swap(x,y);
for(p=1,x[sa[0]]=0,i=1;i<n;i++) //更新新的名次数组x
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
void calc(int *r,int n) //长度为n的字符串r
{
int i,j,k=0;
for(i=1;i<=n;i++) rank[sa[i]]=i;
for(i=0;i<n;height[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
struct Query
{
int len,start; //该点名串起始下标为start,长度为len
}query[MAXN];
int num[MAXN];
int belong[MAXN]; //belong[i]=字符串的第i位所属的猫的编号
int ans[MAXN]; //ans[i]=第i只猫被点名次数
int vis[MAXN]; //vis[i]=第i只猫所属的点名串编号
int main()
{
int len=0,n,m;
scanf("%d%d",&n,&m);
int breaker=11000;
for(int i=1;i<=n;i++)
{
int l;
scanf("%d",&l);
for(int j=1;j<=l;j++)
{
belong[len]=i;
scanf("%d",&num[len++]);
}
num[len++]=++breaker;
scanf("%d",&l);
for(int j=1;j<=l;j++)
{
belong[len]=i;
scanf("%d",&num[len++]);
}
num[len++]=++breaker;
}
for(int i=1;i<=m;i++)
{
scanf("%d",&query[i].len);
query[i].start=len;
for(int j=1;j<=query[i].len;j++)
scanf("%d",&num[len++]);
num[len++]=++breaker;
}
SA(num,len,200000);
calc(num,len-1);
for(int i=1;i<=m;i++)
{
int L,R;
L=R=rank[query[i].start];
while(height[L]>=query[i].len) L--;
while(height[R]>=query[i].len) R++;
R--;
int sum=0;
for(int j=L;j<=R;j++)
{
if(belong[sa[j]]) //排名为j的后缀的开头字符是属于某个猫的
{
if(vis[belong[sa[j]]]!=i) //这个字符所属的串所属的猫还没被计入
{
vis[belong[sa[j]]]=i;
sum++;
ans[belong[sa[j]]]++;
}
}
}
printf("%d\n",sum);
}
for(int i=1;i<=n;i++) printf("%d%c",ans[i],i==n?'\n':' ');
return 0;
}