1. AC 自动机 ACAM

1.1. 简介

AC 自动机用于解决多模式串匹配问题,例如求多个模式串在文本串中的出现次数。显著地,它的应用实际上非常广泛。

借助 KMP 的思想,我们对 Trie 树上的每个节点构造其失配指针 \(fail_i\),指向对于当前字符串的最长后缀(其他(前缀)作为当前串后缀的最长的一个),显著地,每个节点的失配指针都指向一个更短的状态。当这样的后缀不存在时,失配指针会指向表示空串的根节点。

考虑如何构建 \(fail_i\):

根据每个节点的失配指针都指向一个更短的状态这个性质,考虑用 BFS 解决 \(fail_i\) 的构建,对于当前节点 \(now\) 来说,假设深度较小的节点都已经被处理完了。

现在假设当前节点 \(i\) 由 \(fa_i\) 经过字符 \(ch\) 转移过来,使 \(fail_i\leftarrow trans(fail_{fa_i},ch)\),若不存在 \(fail_{fa_i}\) 通过 \(ch\) 转移到的某一节点,则尝试使 \(fail_i\leftarrow trans(fail_{fail_{fa_i}},ch)\)。直到 \(fail\) 指向根节点,说明根本不存在合法前缀,我们使 \(fail_i\leftarrow rt\)。

特殊地,若不存在 \(trans(fa,ch)\) 这个转移方式,则直接令 \(trans(fa,ch)\leftarrow trans(fail_{fa_i},ch)\)。

1.2. 常见技巧

1.2.1 fail 树的性质

构建的 \(fail\) 指针会形成一棵树,称为 fail 树。这不是废话吗。

  1. fail 树为一颗有根树,可以进行树剖等树上操作。
  2. 对于节点 \(p\) 与其对应字符串 \(t_p\),对于任意一个子树内节点 \(q\),都有 \(t_p\) 是 \(t_q\) 的后缀。逆命题亦成立。
  3. 设 \(cnt_p\) 表示作为 \(t_p\) 后缀的字符串数量。若无重复串,则 \(cnt_p\) 为树上节点 \(p\) 到根节点上字符串节点数量。

1.2.2 应用

ACAM 可以与 DP 结合,在自动机中进行 DP。

1.3. 例题

\(\color{blueviolet}{P5357}\)

时间瓶颈在于每次跳 \(tail\) 的重复访问,考虑经过每个点时记录权值,最后一起统计,可以采用 DFS 或者拓扑排序。

$\text{Code}$:
#include<bits/stdc++.h>
#define LL long long
#define UN unsigned
using namespace std;
//--------------------//
//IO
inline int rd()
{
int ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
//--------------------//
const int N=2e6+5,Ch=30; int n;
char str[N];
int id[N],ans[N];
struct Edge
{
int to,nex;
}edge[N];
int etot,head[N];
void add(int from,int to)
{
edge[++etot]={to,head[from]};
head[from]=etot;
return;
}
struct ACAM
{
struct Trie_Node
{
int nex[Ch];
int fail,flag,cnt;
}t[N];
int tot,fcnt;
void insert(char *s,int temp)
{
int now=0,len=strlen(s+1);
for(int i=1;i<=len;i++)
{
if(!t[now].nex[s[i]-'a'+1])
t[now].nex[s[i]-'a'+1]=++tot;
now=t[now].nex[s[i]-'a'+1];
}
if(!t[now].flag)
t[now].flag=++fcnt;
id[temp]=t[now].flag;
return;
} void get_fail()
{
queue<int>q;
for(int i=1;i<=26;i++)
{
if(t[0].nex[i])
q.push(t[0].nex[i]);
}
while(!q.empty())
{
int now=q.front();
q.pop();
for(int to,i=1;i<=26;i++)
{
to=t[now].nex[i];
if(!to)
{
t[now].nex[i]=t[t[now].fail].nex[i];
continue;
}
t[to].fail=t[t[now].fail].nex[i];
q.push(to);
}
}
return;
}
void get_ans(char *s)
{
int len=strlen(s+1),now=0;
for(int i=1;i<=len;i++)
{
now=t[now].nex[s[i]-'a'+1];
t[now].cnt++;
}
return;
}
void build()
{
for(int i=1;i<=tot;i++)
add(t[i].fail,i);
return;
}
void DFS(int now)
{
//printf("%d %d\n",now,t[now].cnt);
for(int to,i=head[now];i;i=edge[i].nex)
{
to=edge[i].to;
DFS(to);
t[now].cnt+=t[to].cnt;
}
if(t[now].flag)
ans[t[now].flag]=t[now].cnt;
return;
}
}A;
//--------------------//
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%s",str+1),A.insert(str,i);
A.get_fail();
scanf("%s",str+1);
A.get_ans(str);
A.build();
A.DFS(0);
for(int i=1;i<=n;i++)
printf("%d\n",ans[id[i]]);
return 0;
}

2. 后缀自动机 SAM

2.1. 简介

2.1.1. 基本定义与结论

SAM 一般用于在线性时间内解决如下问题:

  • 在一个字符串中求另一字符串出现的位置。
  • 一个字符串的本质不同的子串个数。

SAM 的定义:一个长为 \(n\) 的字符串 \(s\) 的SAM 是一个接受 \(s\) 的所有后缀最小的有限状态自动机。

较为人话的说法:

  • SAM 是一张有向无环图。每个节点为一个状态,边则为状态间的转移
  • 存在一个源点 \(t_0\),称作初始状态,其他状态均可从 \(t_0\) 出发到达。
  • 每个转移(边)对应一个字符(一条路径表示一个字符串),从一个状态(节点)出发的转移均不同。
  • 从初始状态 \(t_0\) 出发,最终转移到一个终止状态,则此径代表的字符串一定是原字符串 \(s\) 的一个后缀,\(s\) 的每个后缀都可以用一条这种路径表示。
  • 满足上述条件的自动机中,SAM 的节点数量最少

SAM 的较为重要的一条性质:

  • 初始状态出发到任意状态的路径与串 \(s\) 的所有子串(本质不同)一一对应

接下来给出一些定义和符号表示:

  • \(c_{p,q}\):转移 \(p\to q\) 代表的字符
  • \(\mathrm{st}(p,c)\):状态 \(p\) 经过字符 \(c\) 转移所到达的状态
  • \(\mathrm{endpos}(t)\):字符串 \(t\) 在原字符串中所有结束位置的集合
  • 等价类:对于 \(\mathrm{endpos}\) 集合相同的子串,我们将它们划分为一个等价类,作为一个状态
  • \(\mathrm{ep}(p)\):状态 \(p\) 所对应的 \(\mathrm{endpos}\) 集合
  • \(\mathrm{substr}(p)\):状态 \(p\) 所表示的所有子串的集合
  • \(\mathrm{longest}(p)\):状态 \(p\) 所表示的所有子串中,长度最长的那一个子串
  • \(\mathrm{shortest}(p)\):状态 \(p\) 所表示的所有子串中,长度最短的那一个子串
  • \(\mathrm{len}(p)\):状态 \(p\) 所表示的所有子串中,长度最长的那一个子串的长度
  • \(\mathrm{minlen}(p)\):状态 \(p\) 所表示的所有子串中,长度最短的那一个子串的长度

方便理解,我们再次整理上述定义。SAM 的每个状态对应一个等价类,即 \(\mathrm{endpos}\) 集合相同的子串所组成的状态。具体地,我们给出例子。假设现有串 \(s=\texttt{"abcab"}\),则 \(\mathrm{endpos}(\texttt{"ab"})=\mathrm{endpos}(\texttt{"b"})={2,5}\),而串 \(\texttt{"ab"},\texttt{"b"}\) 便属于统一等价类。

\(\mathrm{longest}(p),\mathrm{shortest}(p),\mathrm{len}(p),\mathrm{minlen}(p)\) 则描述了状态 \(p\) 对应的字符串集合 \(\mathrm{substr}(p)\) 中最长、最短的字符串以及它们的长度。

下面介绍两个结论:

  • 对于两个非空字符串 \(x,y\)(\(|x|\leq |y|\)),要么有 \(\mathrm{endpos}(x)\subseteq \mathrm{endpos}(y)\),要么有 \(\mathrm{endpos}(x)\cup\mathrm{endpos}(y)=\varnothing\)
  • 对于一个状态 \(p\),其包含的字符串长度连续,且较短者是较长者的后缀。

两个结论的证明过程并不复杂,简单思考也可以感性理解,在这里不给出具体证明,具体可参考 Alex_Wei 的博客或者 OI-Wiki,链接见参考资料部分。

总结以上两个性质有:

  • 对于一个子串 \(t\) 的所有后缀,其 \(endpos\) 集合的大小随着后缀长度减小而单调不降,并且较大的集合包含较小的集合。简单定性分析,当后缀越短时,约束条件越宽松,出现位置更可能多。

根据上面的性质,我们给出 SAM 中最核心的定义:

  • 后缀链接 \(\mathrm{link}(p)\):对于所有 \(\mathrm{ep}(p)\subseteq\mathrm{ep}(q)\),\(\mathrm{link}(p)\) 指向 \(\mathrm{len}(q)\) 最大的那个 \(q\)。

稍微直观一点理解我们仍然用一个例子,假设现有串 \(s=\texttt{"babcab"}\)

我们假设状态 \(p\) 对应着我们的字符串集合 \(\{\texttt{"cab"}\}\),对应有 \(\mathrm{ep}(p)=\{6\}\)。

状态 \(a\) 对应字符串集合 \(\{\texttt{"ab"}\}\),对应有 \(\mathrm{ep}(a)=\{3,6\}\)。

状态 \(b\) 对应字符串集合 \(\{\texttt{"b"}\}\),对应有 \(\mathrm{ep}(b)=\{1,3,6\}\)。

根据刚才的定义,现有 \(\mathrm{ep}(p)\subseteq\mathrm{ep}(a),\mathrm{ep}(p)\subseteq\mathrm{ep}(b)\),且只有 \(a,b\) 两状态的 \(\mathrm{ep}\) 包含状态 \(p\) 的 \(\mathrm{ep}\),又有 \(\mathrm{len}(a)>\mathrm{len}(b)\),所以 \(\mathrm{link}(p)\) 应指向状态 \(a\)。

再次重复一下 \(\mathrm{link}(p)\) 的意义,它指向了状态 \(p\) 的所有后缀状态中(最长)长度最大的那个,易知 \(\mathrm{len}(\mathrm{link}(p))+1=\mathrm{minlen}(p)\)。

对于后缀链接有这样一条性质:

  • 所有后缀链接形成一颗以 \(t_0\) 为根的树。(\(t_0\) 是我们最开始定义的初始状态,它包含了空串。)

显著的,对于任意状态(除了 \(t_0\)),沿着后缀链接移动总会达到一个 \(\mathrm{len}\) 更短的状态,直到 \(t_0\)。

后缀链接构成的树本质上是 \(\mathrm{endpos}\) 集合构成的一棵树,我们一般称为 Parent 树。

2.1.2. 关键结论

这部分主要摘自 Alex_wei 的博客,理解构建 SAM 的过程需要理解此部分的结论。如果你能较为直观地理解 Parent 树,那么这部分的结论都很显然,大部分证明请参考 Alex_wei 的博客。

第一组结论:

  • 从任意状态 \(p\) 出发通过后缀链接跳转到 \(t_0\) 的路径,所有路径上的状态 \(q\) 的 \([\mathrm{minlen}(q),\mathrm{len}(q)]\) 无交集,并且范围随着在 Parent 上的深度减小而减小,并且他们的并集形成一个连续区间 \([0,\mathrm{len}(p)]\)。
  • 从任意状态 \(p\) 出发通过后缀链接跳转到 \(t_0\) 的路径,所有路径上的状态 \(q\) 的 \(\mathrm{substr}(q)\) 的并集为 \(\mathrm{longest}(p)\) 的所有后缀

第二组结论:

  • 有任意状态 \(p\) 使得有从 \(p\) 到 \(q\) 的转移,对于 \(\forall t_p\in \mathrm{substr}(p)\),有 \(t_p+c_{p,q}\in\mathrm{substr}(q)\)。
  • 对于 \(\forall t_q\in\mathrm{substr}(q)\),存在且只存在一个状态 \(p\) 使得有从 \(p\) 到 \(q\) 的转移,并且 \(\exist t_p\in \mathrm{substr}(p)\) 使得 \(t_p+c_{p,q}=t_q\)。

第三组结论:

  • 不存在从状态 \(p\) 到状态 \(q\) 的转移使得 \(\mathrm{len}(p)+1>\mathrm{len}(q)\)。
  • 存在唯一状态 \(p\),有 \(p\) 到 \(q\) 的转移使得 \(\mathrm{len}(p)+1=\mathrm{len}(q)\)。
  • 存在唯一状态 \(p\),有 \(p\) 到 \(q\) 的转移使得 \(\mathrm{minlen}(p)+1=\mathrm{minlen}(q)\)。

在给出第四组结论之前,我们先给出两个定义:

  • \(\mathrm{maxtrans}(q)\):有 \(p\) 到 \(q\) 的转移使得 \(\mathrm{len}(p)+1=\mathrm{len}(q)\) 的唯一 \(p\)。
  • \(\mathrm{mintrans}(q)\):有 \(p\) 到 \(q\) 的转移使得 \(\mathrm{minlen}(p)+1=\mathrm{minlen}(q)\) 的唯一 \(p\)。

第四组结论:

  • 对于转移 \(p\to q\),一定有 \(p\) 在 Parent 树上为 \(\mathrm{maxtrans}(q)\) 或其祖先。
  • 对于转移 \(p\to q\),一定有 \(p\) 在 Parent 树上为 \(\mathrm{mintrans}(q)\) 或其子树内节点。
  • 对于转移 \(p\to q\),所有这样的 \(p\) 在 Parent 树上构成了一条深度递减链,即 \(\mathrm{mintrans}(q)\to\mathrm{maxtrans}(q)\)。

并不难理解,考虑到 Parent 树的定义以及性质,一条从上到下的链中字符串长度连续并且都为链底长串的子串。

2.1.3 SAM 的构建

至此为止,我们可以用以上的所有性质来构建 SAM 了。

我们考虑在前缀串 \(s[1,i-1]\) 的 SAM 基础上插入当前字符更新整个 SAM。

设上一状态(目前已插入的前缀所在状态)为 \(las\),当前状态为 \(cur\),状态总数为 \(tot\)。初始时 \(las,cnt\) 均为 \(1\),即我们设初始状态 \(t_0=1\)。

我们使先新建编号为 \(cur\) 赋值,\(cur\) 表示的是以当前插入字符结尾前缀的状态,然后令 \(p\leftarrow las\),\(p\) 表示我们现在更新到的节点。

考虑转移边的处理,我们将 \(p\) 沿着 Parent 树向上跳,可以保证的是每到一个节点都是 \(s[1,i-1]\) 的后缀,所以我们要更新其向 \(s_i\) 的转移,若其没有此转移,我们就为其新建出边,并继续沿着 Parent 向上跳转。直到我们到一个节点存在此转移,说明再往上的节点都有此转移,就不必再更新了。

接下来考虑 Parent 树边的构建,我们分三种情况讨论。


情况一:

不存在一个 \(p\) 有以 \(s_i\) 的转移。

这种情况存在且只存在于 \(s_i\) 这个字符从未被加入过,我们令 \(\mathrm{link}(cur)\leftarrow t_0\) 即可。


情况二:

存在 \(p\) 有以 \(s_i\) 的转移,令 \(q=\mathrm{st}(p,s_i)\),且 \(\mathrm{len}(p)+1=\mathrm{len}(q)\)。

我们设 \(las\to t_0\) Parent 树上的路径 \(p\) 的前一个状态有 \(p'\),并且 \(p'\) 已经新建了 \(s_i\) 的转移到 \(cur\),根据 Parent 性质有 \(\mathrm{minlen}(cur)=\mathrm{len}(p')+1=(\mathrm{len}(p)+1)+1=\mathrm{len}(q)+1\),根据定义令 \(\mathrm{link}(cur)\leftarrow q\)。


情况三:

存在 \(p\) 有以 \(s_i\) 的转移,令 \(q=\mathrm{st}(p,s_i)\),且 \(\mathrm{len}(p)+1\not=\mathrm{len}(q)\)。

当 \(\mathrm{len}(p)+1\not=\mathrm{len}(q)\),只能存在 \(\mathrm{len}(p)+1<\mathrm{len}(q)\)。状态 \(q\) 中有一部分是无法转移到我们当前状态 \(cur\) 的,可以理解为 \(\mathrm{substr}(q)\) 不全为 \(\mathrm{substr}(cur)\) 的后缀,因为在 \(q\) 中存在以除 \(p\) 之外的状态转移过来的部分。我们考虑将 \(q\) 分为小于等于 \(\mathrm{len}(p)+1\) 和大于 \(\mathrm{len}(p)+1\) 的两部分,并新建状态 \(cl\) 存储小于等于 \(\mathrm{len}(p)+1\) 的部分。对于继承我们需要进行以下操作:

  • \(cl\) 保存所有 \(q\) 向外的转移。
  • \(\mathrm{len}(cl)\) 应等于 \(\mathrm{len}(p)+1\)。
  • \(\mathrm{link}(cl)\) 应等于原来的 \(\mathrm{link}(q)\)。
  • 对于 Parent 树上 \(p\to t\) 路径上的状态,我们也应该将原指向 \(q\) 的转移指向 \(cl\)。
  • \(\mathrm{link}(q),\mathrm{link}(cur)\) 应等于 \(cl\)。

在构建完 Parent 树边后,我们使 \(las\leftarrow cur\),退出构建即可。

2.1.4. SAM 的时空间限制

对于 SAM 构造、使用时间复杂度为线性的证明略复杂,仍是可参考 Alex_Wei 的博客或者 OI-Wiki。

因为每次加入字符最多新建两个节点,所以空间应当开双倍,特殊地,当字符集很大时,可以用 map 维护转移。

2.2. 常用技巧

2.2.1. 求本质不同子串个数

考虑每个子串存在且只存在于一个状态,考虑计数所有状态中的子串总和,对于每个状态 \(i\) 有答案 \(\sum\mathrm{len}(i)-\mathrm{len}(\mathrm{link}(i))\)。

另一种考虑方式,SAM 上一条路径对应一个子串,对于每个状态求一下以此状态结尾的路径数量,最后求和,可以考虑用拓扑排序。(相当于转化为所有前缀的后缀个数。)

2.2.2. 解决匹配串问题

较为简单的,直接在 SAM 上各种跑,失配就跳 Parent,与 KMP 的思想相似。

2.2.3. 求 \(\mathrm{endpos}\) 集合大小

每加入一个新状态 \(cur\),为其计数器打上 \(1\),SAM 构建好后求一下 Parent 子树内计数器和即可。

2.3. 例题

\(\color{blueviolet}{P3804}\)

SAM 板子。

$\text{Code}$:
#include<bits/stdc++.h>
#define LL long long
#define UN unsigned
using namespace std;
//--------------------//
//IO
inline int rd()
{
int ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
//--------------------//
const int N=1e6+5,N2=2e6+5; struct Edge
{
int to,nex;
}edge[N2];
int etot,head[N2];
void add(int from,int to)
{
edge[++etot]={to,head[from]};
head[from]=etot;
return;
} char s[N];
struct SAM
{
struct SAM_Node
{
int nex[30];
int len,fa;
int cnt;
}a[N2];
int las=1,tot=1;
void insert(char ch)
{
int it=ch-'a'+1,p=las;
int cur=++tot;
a[cur].len=a[las].len+1,las=cur,a[cur].cnt=1;
while(!a[p].nex[it]&&p)
a[p].nex[it]=cur,p=a[p].fa;
if(!p)
{
a[cur].fa=1;
return;
}
int q=a[p].nex[it];
if(a[p].len+1==a[q].len)
{
a[cur].fa=q;
return;
}
int cl=++tot;
a[cl]=a[q],a[cl].cnt=0,a[cl].len=a[p].len+1;
a[cur].fa=a[q].fa=cl;
while(a[p].nex[it]==q&&p)
a[p].nex[it]=cl,p=a[p].fa;
return;
}
void build()
{
for(int i=1;i<=tot;i++)
add(a[i].fa,i);
return;
}
LL ans=0;
void DFS(int now)
{
for(int to,i=head[now];i;i=edge[i].nex)
{
to=edge[i].to;
DFS(to);
a[now].cnt+=a[to].cnt;
}
if(a[now].cnt!=1)
ans=max(ans,1LL*a[now].cnt*a[now].len);
return;
}
}S;
//--------------------//
int main()
{
scanf("%s",s+1);
int len=strlen(s+1);
for(int i=1;i<=len;i++)
S.insert(s[i]);
S.build();
S.DFS(1);
printf("%lld",S.ans);
return 0;
}

参考资料

\(\mathcal{Alex\_Wei's\ Blog}\)

\(\mathcal{OI-Wiki}\)

「Note」字符串方向 - 自动机相关的更多相关文章

  1. 【LOJ】#3095. 「SNOI2019」字符串

    LOJ#3095. 「SNOI2019」字符串 如果两个串\(i,j\)比较\(i < j\),如果离\(a_{i}\)最近的不同的数是\(a_{k}\),如果\(j < k\)那么\(i ...

  2. 「JSOI2015」字符串树

    「JSOI2015」字符串树 传送门 显然可以树上差分. 我们对于树上每一条从根出发的路径都开一 棵 \(\text{Trie}\) 树,那么我们就只需要在 \(\text{Trie}\) 树中插入一 ...

  3. LOJ 3055 「HNOI2019」JOJO—— kmp自动机+主席树

    题目:https://loj.ac/problem/3055 先写了暴力.本来想的是 n<=300 的那个在树上暴力维护好整个字符串, x=1 的那个用主席树维护好字符串和 nxt 数组.但 x ...

  4. LG2444/BZOJ2938 「POI2000」病毒 AC自动机

    问题描述 LG2444 BZOJ2938 I \(\mathrm{AC}\)自动机 \(\mathrm{AC}\)自动机是一种多模式串匹配算法,本萌新今天刚学了它qwq 约定在构造\(\mathrm{ ...

  5. 「NOTE」常系数齐次线性递推

    要不是考到了,我还没发现这玩意我不是很会-- # 前置 多项式取模: 矩阵快速幂. # 常系数齐次线性递推 描述的是这么一个问题,给定数列 \(c_1,c_2,\dots,c_k\) 以及数列 \(f ...

  6. 「Foundation」字符串

    一.Foundation框架中一些常用的类 字符串型: NSString:不可变字符串 NSMutableString:可变字符串 集合型: 1)NSArray:OC不可变数组  NSMutableA ...

  7. 【LOJ】#2278. 「HAOI2017」字符串

    题解 好神仙的题啊 感觉转二维平面能想到,算重复情况的方法真想不到啊 通过扒stdcall代码获得的题解QAQQQQ 我们先把\(p_i\)正串反串建出一个AC自动机来 然后我们把s串放在上面跑匹配, ...

  8. 「Python」字符串操作内置函数

    目录: capitalize casefold center count encode decode endswith expandtabs find format format_map index ...

  9. 「Redis」字符串

    原文链接:https://www.changxuan.top/?p=1109 简介 Redis 中自定义的字符串结构. 字符串是 Redis 中最常用的一种数据类型,在 Redis 中专门封装了一个字 ...

  10. 「SNOI2019」字符串

    题目 看起来非常一眼啊,我们完全可以\(std::sort\)来解决这歌问题 于是现在的问题转化成了比较函数怎么写 随便画一下就会发现前面的好几位是一样的,后面的好几位也是一样,只需要比较中间的一段子 ...

随机推荐

  1. go 地址对齐保证

    unsafe标准库包 func Alignof(variable ArbitraryType) uintptr. 此函数用来取得一个值在内存中的地址对齐保证(address alignment gua ...

  2. Laravel 配置连接多个数据库以及如何使用

    目录 配置连接 配置 .env 文件 配置 \config\database.php 文件 使用 Schema Query Eloquent 配置连接 配置 .env 文件 /* 这部分是默认的数据库 ...

  3. MOS管的引脚,G、S、D分别代表什么?

    引脚解析: G:gate 栅极,N沟道的电源一般接在D. S:source 源极,输出S,P沟道的电源一般接在S. D:drain 漏极,输出D.增强耗尽接法基本一样. mos管是金属(metal)- ...

  4. Delphi 禁止重复运行程序的方法

    第一种方法,使用"过程调用" procedure Del; // 自定义过程 var Mutex: THandle; begin Mutex := CreateMutex(nil, ...

  5. Open diary(每天更新)

    .col-md-8 img { display: none } .comment img { display: unset } 这是一个open diary,就是公开日记. 为什么标题用英文呢?因为觉 ...

  6. [每日算法 - 华为机试] leetcode345 :反转字符串中的元音字母「双指针」

    入口 力扣https://leetcode.cn/problems/reverse-vowels-of-a-string/submissions/ 题目描述 给你一个字符串 s ,仅反转字符串中的所有 ...

  7. 常见行为面试题-Why do you want to work here?

    Why do you want this job?/Why do you want to work here? Keys to answer the question Research the com ...

  8. 【SpringMVC】运行流程

    SpringMVC 运行流程 在 Spring 的环境下使用 SpringMVC Bean 被创建两次? Spring 的 IOC 容器不应该扫描 SpringMVC 中的 bean, 对应的 Spr ...

  9. C# 工业视觉开发必刷20道 Halcon 面试题

    前言 随着工业4.0的深入推进,越来越多的企业开始重视智能制造和自动化生产.在这个背景下,对具备C#和Halcon开发经验的专业人才需求也日益增长. 为了帮助广大 C#工业视觉开发的朋友更好地备战面试 ...

  10. TM1637读取键值调试笔记

      因为项目原因需要用到TM1637,实现驱动数码管和按键扫描,参考了网络上搜索到的一些例程,基本实现了功能要求,能够实现数码管点亮和按键扫描.   调试过程中也出现一些问题,现在描述一下问题和解决方 ...