0%

[UER#11] 企鹅游戏

模拟赛遇到的

代码难度并不大,但是是有思维难度的。

题目链接。

题意

现在有 个单词 与 个文本串 。

设 为 在 中的出现次数,对于每一个 :输出 。

设 。

互不相同。

题解

暴力

首先来讲 怎么做。

第一步是一个经典结论, 的取值最多有 种。

也就是说,对于一个文本串 ,最多会被匹配 次。

那么我们首先把 的 AC 自动机建出来,并记 表示,节点 的所有祖先(不包括自己)中,深度最深的,至少对应了一个单词的节点。

那么对于每一个询问串 ,我们还是先在自动机上匹配一遍。

正常计算匹配次数时,我们是暴力对 AC 自动机上所有节点做拓扑排序。

现在我们换一个思路,利用 数组。

在上面匹配的时候,每到一个节点,就在 nx 树上 dfs,并直接统计答案。

在 dfs 的过程中,若我们遍历了 次节点 ,说明这个字符串被匹配了 遍。

这样的话,我们的时间复杂度就是 匹配次数 的。

可以过 分,好耶。

我场上写的根号分治这部分半分钟都跑不出来

正解

首先正解有一个结论:对于所有问串,能匹配上至少一次的单词,的个数和不超过 。

形式化地:。

好,我们来证一下。

对于 的单词 ,这样的单词最多只有 个。

若 在文本串 中出现过,则 ,这样的文本串也最多只有 个。

所以这部分单词贡献的次数为

对于 的单词 ,在每一个文本串 中,一定最多只会匹配 次,因为 只有 个长度 的子串。

所以对于这种单词,贡献的次数最多也为 。

这意味这什么呢?

对于我们上面的暴力做法,只要对于每一次询问,不经过重复的节点,那么时间复杂度就是 的!

这个很简单,我们 dfs 的时候标一个 vis,dfs 的过程中直接遇到遍历过的节点,直接 return 掉。

可能有一个细节,就是这里要统计答案必须要拓扑排序,那么拓扑序如何确定呢?

比较简单,每一次 dfs 的时候,把这一条 dfs 的链,按照从深到浅的顺序,拼到当前拓扑序的前面,就是一个新的合法拓扑序

时间复杂度 ,官方题解说可以做到 ,但我折磨菜,显然不会。

这里统计答案需要快速计算 ,可以使用光速幂解决。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fll
#define debug(x) cerr<<#x<<"="<<x<<endl
using namespace std;
using uint=unsigned int;
using ll=long long;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int L=200001;
uint p1[200005];
uint p2[200005];
int son[2000001][26];
int fail[2000001];
int id[2000001];
int nx[2000001];
char s[200005];
int T[2000001],top;
int vis[2000001];
int v[2000001];
int root;
int tim;
int cnt;
int n,m;
void ins(int &p,char *s,int i)
{
if(!p) p=++cnt;
if(!(*s))
{
id[p]=i;
return ;
}
ins(son[p][(*s)-'a'],s+1,i);
}
inline void bfs()
{
queue<int>que;fail[root]=root;
for(int i=0;i<26;i++)
{
if(!son[root][i]) son[root][i]=root;
else fail[son[root][i]]=root,que.push(son[root][i]);
}
while(!que.empty())
{
int u=que.front();que.pop();
if(id[fail[u]]) nx[u]=fail[u];
else nx[u]=nx[fail[u]];
for(int i=0;i<26;i++)
{
if(!son[u][i])
{
son[u][i]=son[fail[u]][i];
continue;
}
fail[son[u][i]]=son[fail[u]][i];
que.push(son[u][i]);
}
}
}
void dfs(int num)
{
if(!num||vis[num]==tim) return ;
vis[num]=tim;
dfs(nx[num]);
T[++top]=num;
}
inline uint get(ll v)
{
return p2[v/L]*p1[v%L];
}
inline uint solve()
{
int now=1,ok=0;tim++;
for(int i=1;s[i];i++)
{
now=son[now][s[i]-'a'];
v[now]++,dfs(now);
}
uint ans=0;
while(top)
{
int &p=T[top--];
v[nx[p]]+=v[p];
if(id[p]) ans+=get((ll)id[p]*v[p]),ok++;
v[p]=0;
}
return ans+n-ok;
}
int main()
{
read();
n=read(),m=read();
for(int i=1;i<=n;i++)
{
scanf("%s",s);
ins(root,s,i);
}

bfs();
p1[0]=p2[0]=1;
for(int i=1;i<=L;i++) p1[i]=p1[i-1]*3;
for(int i=1;i<=L;i++) p2[i]=p2[i-1]*p1[L];

for(int i=1;i<=m;i++)
{
scanf("%s",s+1);
printf("%u\n",solve());
}
return 0;
}

感谢观看!