加强版数据包满足病毒代码段总长度不超过 $1000000$,且绑包。
暴力 $\text{84pts}$ 做法
第一眼发现不会做,我们考虑暴力。
枚举长度
我们可以枚举每一位,直到枚举到第 $2\times len$ 时结束代表存在无限长的安全代码,其中 $len$ 表示病毒代码段的最长长度。
为什么
假设存在一段有限长度的安全代码,满足其中没有出现任何病毒代码段。
那么显然其长度至少得是 $len$ 才能够保证自己中间不会出现病毒代码段。
但是病毒代码段可能会出现在“结尾一半,剩下一半在开头”的情况。如果不存在这种情况,我们就可以无限循环这一段有限的安全代码段从而构造出无限安全代码段。
所以我们在构造时需要杜绝这种情况发生。我们可以通过构造到 $2\times len$ 来避免。如果此时末尾还会出现的话,那么在长度为 $len$ 时就已经出现过了,不会搜索到 $2\times len$。
因此,需要枚举到第 $2\times len$ 位。
暴力匹配
既然是暴力,就暴力到底。
我们可以使用 string 自带的 .find() 函数来查找。
即:
1
2
3
4
5
6
7
bool check(){
for(int i=1;i<=n;i++){
if(t.find(s[i])!=string::npos){
return false;
}
}return true;
}
不难发现,这样检查一次的时间复杂度是 $\mathcal O\left(\vert t\vert\times \sum\vert s\vert\right)$ 的。
时间复杂度
枚举复杂度:$\mathcal O\left(\max\left(\vert s\vert\right)\right)$。
检查时间复杂度:$\mathcal O\left(\vert t\vert\times \sum\vert s\vert\right)$。
总时间复杂度:$\mathcal O\left(\dfrac{(1+\max\left(\vert s\vert\right))\times \max\left(\vert s\vert\right)}{2}\times \sum\vert s\vert\right)$。
考虑到 $\sum\vert s\vert\leq 3\times 10^4$,通过不了此题。
AC 自动机匹配优化
此部分之所以能够成功仅仅是因为数据范围过小,不提供参考代码。
我们可以通过 AC 自动机来优化多字符串匹配。
那样检查的时间复杂度就降为了 $\mathcal O\left(\vert t\vert+ \sum\vert s\vert\right)$。
总时间复杂度降为:$\mathcal O\left(\dfrac{(1+\max\left(\vert s\vert\right))\times \max\left(\vert s\vert\right)}{2}+\sum\vert s\vert\right)$。
因为 $\max(\vert s\vert)\leq \vert s\vert\leq3\times 10^4$,则最大值为 $\mathcal O\left(\dfrac{(1+3\times 10^4)\times 3\times 10^4}{2}+3\times 10^4\right)$,约为 $3\times 10^8$,可以通过。
参考代码
$\text{84pts}$ 参考代码
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
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=1500;
int n,len;
string s[N+1],t;
bool check(){
for(int i=1;i<=n;i++){
if(t.find(s[i])!=string::npos){
return false;
}
}return true;
}
bool dfs(int p){
if(p>=2*len){
return true;
}
t.append("1");
if(check()){
if(dfs(p+1))return true;
}
t.back()='0';
if(check()){
if(dfs(p+1))return true;
}t.erase(t.size()-1,1);
return false;
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
scanf("%d",&n);
if(n>1000){
printf("NIE\n");
return 0;
}
for(int i=1;i<=n;i++){
cin>>s[i];
len=max(len,(int)s[i].size());
}
printf("%s\n",(dfs(0)?"TAK":"NIE"));
/*fclose(stdin);
fclose(stdout);*/
return 0;
}
正解 $\text{100pts}$ 做法
前置知识:AC 自动机/Trie 图
不会请看此处。
Trie 图上找环
首先我们假设一定构造出了一个合法的无限长的字符串 $t$。
那么在 Trie 图上上匹配字符串 $t$ 时,根据题意肯定是永远匹配不到的。
但是 Trie 图的节点数显然是有限的,而匹配时又走过了无限个点,显然 Trie 图上出现了一个环。
暴力的思想
暴力会搜索到 $2\times len$,而 Trie 树上最长的从根节点到叶节点的长度显然为 $len$。这同样也意味着出现了环。
因此,我们建出 Trie 图后在图上通过 DFS 找环即可。
注意:这个环上的节点不能包括病毒代码段末尾节点。否则代表包括了病毒代码段。
DFS 找环
需要注意的是,已经走过的点不需要再走,否则时间复杂度会退化为 $\mathcal O(n^2)$。
具体而言,维护 $vis_x$ 为 ${0,1,2}$ 中的值:
- $vis_x=0$:未访问。
- $vis_x=1$:已访问,且在 DFS 栈中。
- $vis_x=2$:已访问,且不在 DFS 栈中。
比如说一条路径是:$a\to b\to c\to d\to \cdots$,没有找到环。而后来又从节点 $e$ 开始走了 $e\to c\to \cdots$,此时($vis_c=2$ 时)继续从点 $c$ 开始走是一定找不到环的。
证明
假设有环存在,点 $c$ 是该环的一个节点。
那么环的路径肯定形如 $c\to y_1\to y_2\to y_3\to\cdots\to y_k\to c$。这如果存在肯定能够在 $a\to b\to c$ 之后找到,但是没找到(因为 $vis_c=2$,代表搜索过,但是递归程序没有结束,代表没有找到),与假设矛盾。
故,$vis_c=2$ 时无需搜索节点 $c$。
因此每个节点都只会访问一次,找环时间复杂度:$\mathcal O(n)$。
注意不要包括病毒代码段末尾节点。
AC 代码
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
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
#define DEBUG 1
#define cerr if(DEBUG)cerr
using namespace std;
constexpr const int N=3e4;
char s[N+1];
struct trie{
struct node{
int m[2];
int fail;
bool flag;
}t[N+1];
int top;
void insert(char *s){
int p=0;
for(int i=0;s[i];i++){
if(!t[p].m[s[i]-'0']){
t[p].m[s[i]-'0']=++top;
}
p=t[p].m[s[i]-'0'];
}t[p].flag=true;
}
int q[N+1],front,rear;
void build(){
for(int i=0;i<2;i++){
if(t[0].m[i])q[rear++]=t[0].m[i];
}
while(front<rear){
int p=q[front++];
for(int i=0;i<2;i++){
if(t[p].m[i]){
t[t[p].m[i]].fail = t[t[p].fail].m[i];
t[t[p].m[i]].flag |= t[t[t[p].fail].m[i]].flag;
q[rear++]=t[p].m[i];
}else{
t[p].m[i] = t[t[p].fail].m[i];
}
}
}
}
bool check(int p=0){
static int vis[N+1];
if(t[p].flag)return false;
if(vis[p]==2)return false;
if(vis[p]==1)return true;
vis[p]=1;
for(int i=0;i<2;i++){
if(check(t[p].m[i]))return true;
}vis[p]=2;
return false;
}
}t;
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
int n;
scanf("%d",&n);
while(n--){
scanf("%s",s);
t.insert(s);
}
t.build();
printf("%s\n",(t.check()?"TAK":"NIE"));
/*fclose(stdin);
fclose(stdout);*/
return 0;
}