字符串
KMP
两个单整串相互匹配。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
string s1,s2;
int nxt[N],ans,len1,len2;
int main()
{
cin>>s1>>s2;
len1=s1.length();
len2=s2.length();
for(int i=1,j=0;i<len2;++i)
{
while(j&&s2[i]!=s2[j]) j=nxt[j];
nxt[i+1]=s2[i]==s2[j]?++j:0;
}
ans=0;
for(int i=0,j=0;i<len1;++i)
{
while(j&&s1[i]!=s2[j]) j=nxt[j];
if(s1[i]==s2[j]&&++j==len2) ans++;
}
cout<<ans<<"\n";
}
结论1:一个长度为n的字符串,最短循环节的长度为n-nxt[n]。
结论2:使每个nxt[i]=nxt[nxt[i]],i-nxt[i]可以得到最大周期。即Q∈≠Q,A∈QQ。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+5;
string s2;
int nxt[N],n;
ll ans;
int main()
{
cin>>n>>s2;
for(int i=1,j=0;i<n;++i)
{
while(j&&s2[i]!=s2[j]) j=nxt[j];
nxt[i+1]=s2[i]==s2[j]?++j:0;
}
for(int i=2;i<=n;++i)
if(nxt[nxt[i]]) nxt[i]=nxt[nxt[i]];
for(int i=2;i<=n;++i)
if(nxt[i]) ans+=(ll)i-nxt[i];
cout<<ans;
}
TRIE
一个整串和多个串匹配
#include<bits/stdc++.h>
using namespace std;
#define maxn 100000+5
string s;
int n,k,tot=0;
int tree[maxn][26]={ };
bool end[maxn];
void charu()
{
int p=0;
for(int i=0;i<s.length();i++)
{
if(!tree[p][s[i]-'a']) tree[p][s[i]-'a']=++tot;
p=tree[p][s[i]-'a'];
}
end[p]=1;
return;
}
bool search()
{
int p=0;
for(int i=0;i<s.length();i++)
{
if(!tree[p][s[i]-'a']) return 0;
p=tree[p][s[i]-'a'];
}
return end[p];
}
int main()
{
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)
{
cin>>s;
charu();
}
for(int i=1;i<=k;i++)
{
cin>>s;
if(search()) printf("Yes\n");
else printf("No\n");
}
}
异或树
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,te,tr,ans,tail[N],xo[N],trie[N*35][2];
struct e_
{
int v,w,pre;
}e[N<<1];
inline void add(int u,int v,int w)
{
e[++te]={v,w,tail[u]};
tail[u]=te;
}
void dfs(int u,int fa)
{
for(int i=tail[u];i;i=e[i].pre)
{
int v=e[i].v,w=e[i].w;
if(v==fa) continue;
xo[v]=xo[u]^w;
dfs(v,u);
}
}
void add(int x)
{
int p=0;
for(int i=1<<30;i;i>>=1)
{
bool y=x&i;
if(!trie[p][y]) trie[p][y]=++tr;
p=trie[p][y];
}
}
int calc(int x)
{
int res=0;
int p=0;
for(int i=1<<30;i;i>>=1)
{
bool y=x&i;
if(trie[p][y^1]) p=trie[p][y^1],res+=i;
else p=trie[p][y];
}
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1,u,v,w;i<n;++i)
{
scanf("%d %d %d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
dfs(1,0);
//直接存到根节点的值,两点异或运算时,lca到根节点部分相当于异或两次,等同于毫无变化
for(int i=1;i<=n;++i) add(xo[i]);
for(int i=1;i<=n;++i) ans=max(ans,calc(xo[i]));
printf("%d",ans);
}
查找s在trie上的前缀,以及包含s的串的个数等于:end[图中所有点]+cnt[结束的点]。
通配符
很毒瘤的一道题。
给定n个字符串,q次询问,每次询问一个包含通配符*的字符串可以与多少给定的字符串匹配。
trie树的时候插入一下后缀,然后把含有某后缀的trie树标的L插入这个后缀对应的vector数组,每次就找后缀对应的vector里L[p]到R[p]的范围,L和R是确定边界用的。
整两个哈希要更精准一点。
#include<bits/stdc++.h>
using namespace std;
const int N=3e6+5,M=1e5+5;
const int B1=193,mod1=998244353,B2=131,mod2=19260817;
string s,ss[M];
int n,Q,len;
int tt,num,t[N][26],L[N],R[N];
struct node
{
int h1,h2;
friend bool operator<(node a,node b)
{
return a.h1!=b.h1?a.h1<b.h1:a.h2<b.h2;
}
}a[M];
vector<node>q[N];
set<node>sum;
map<node,vector<int> >G;
void add()
{
int p=0,x;
q[p].push_back(a[0]);
for(int i=0;i<len;++i)
{
int x=s[i]-'a';
if(!t[p][x]) t[p][x]=++tt;
p=t[p][x];
q[p].push_back(a[i+1]);
}
}
void DFS(int p)
{
L[p]=++num;
for(int i=0;i<q[p].size();++i) G[q[p][i]].push_back(L[p]);
for(int i=0;i<26;++i) if(t[p][i]) DFS(t[p][i]);
R[p]=++num;
}
int ask(int k)
{
int p=0;
len=ss[k].length();
for(int i=0;i<len;++i)
if(ss[k][i]=='*') return upper_bound(G[a[k]].begin(),G[a[k]].end(),R[p])-lower_bound(G[a[k]].begin(),G[a[k]].end(),L[p]);
else
{
int x=ss[k][i]-'a';
if(!t[p][x]) return 0;
p=t[p][x];
}
}
int main()
{
scanf("%d %d",&n,&Q);
for(int i=1;i<=n;++i)
{
cin>>s;
len=s.length();
a[len].h1=a[len].h2=0;
for(int i=len-1;i>=0;--i)
a[i].h1=(1ll*a[i+1].h1*B1+s[i]-'A')%mod1,
a[i].h2=(1ll*a[i+1].h2*B2+s[i]-'A')%mod2;
add();
}
for(int i=1;i<=Q;++i)
{
cin>>ss[i];
len=ss[i].length();
a[i].h1=a[i].h2=0;
for(int j=len-1;j>=0;--j)
if(ss[i][j]=='*') break;
else
a[i].h1=(1ll*a[i].h1*B1+ss[i][j]-'A')%mod1,
a[i].h2=(1ll*a[i].h2*B2+ss[i][j]-'A')%mod2;
sum.insert(a[i]);
}
DFS(0);
for(int i=1;i<=Q;++i) printf("%d\n",ask(i));
}
AC自动机
一个整串的字串与多个字符串匹配,查找trie树上的串有多少个在整串内。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
string s;
int t,n,m,tr,trie[N][30],fail[N],cnt[N];
//字典树||失败指针||某单词出现的次数
void add()
{
int p=0;
for(int i=0;i<s.length();++i)
{
if(!trie[p][s[i]-'a']) trie[p][s[i]-'a']=++tr;
p=trie[p][s[i]-'a'];
}
cnt[p]++;
}
void getfail()
{
deque<int>q;
//将第二层出现了的字母扔进队列
for(int i=0;i<26;++i)
if(trie[0][i])
q.push_back(trie[0][i]);
//fail[now] ->当前节点now的失败指针指向的地方
//tire[now][i] -> 下一个字母为i+'a'的节点的下标为tire[now][i]
while(!q.empty())
{
int u=q.front();
q.pop_front();
//如果这个字母在这里存在,则跳到父节点已匹配串的下一个节点
//即,以本点为结束点,存在的某个前缀和本串某后缀相同的串
//如asha,shp,在asha的h点,先到s对应的fail,即shp中的s点,再到shp中的h点
for(int i=0;i<26;++i)
if(trie[u][i])
{
fail[trie[u][i]]=trie[fail[u]][i];
q.push_back(trie[u][i]);
}
else trie[u][i]=trie[fail[u]][i];
//如果这个字母在这里不存在,就直接连到另一个点的这个点
//如asha,shp,若给出的串为ashp,则先在asha中到p节点
//p不存在,则到父节点对应的下一节点,即shp中的h,再到shp中的p
//如asha,shu,shp,经过两次跳跃同样可以到p点
}
}
int query()
{
int j,p=0,res=0;
for(int i=0;i<s.length();++i)
{
//如果这个节点不存在,由于我们的特殊处理,它会跳转到其他串上存在的地方或者根节点
j=p=trie[p][s[i]-'a'];
//分别以每个点为结束节点跳转,到了根节点或者到过的节点就停止
while(j&&cnt[j]!=-1) res+=cnt[j],cnt[j]=-1,j=fail[j];
}
return res;
}
int main()
{
scanf("%d",&t);
while(t--)
{
memset(cnt,0,sizeof(cnt));
memset(trie,0,sizeof(trie));
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
cin>>s;
add();
}
// cout<<tr<<"\n";
getfail();
cin>>s;
printf("%d\n",query());
}
}
玄武湖畔
Description
在美丽的玄武湖畔,鸡鸣寺边,鸡笼山前,有一块富饶而秀美的土地,人们唤作进香河。相传一日,一缕紫气从天而至,只一瞬间便消失在了进香河中。老人们说,这是玄武神灵将天书藏匿在此。
很多年后,人们终于在进香河地区发现了带有玄武密码的文字。更加神奇的是,这份带有玄武密码的文字,与玄武湖南岸台城的结构有微妙的关联。于是,漫长的破译工作开始了。
经过分析,我们可以用东南西北四个方向来描述台城城砖的摆放,不妨用一个长度为N的序列来描述,序列中的元素分别是‘E’,‘S’,‘W’,‘N’,代表了东南西北四向,我们称之为母串。而神秘的玄武密码是由四象的图案描述而成的M段文字。这里的四象,分别是东之青龙,西之白虎,南之朱雀,北之玄武,对东南西北四向相对应。
现在,考古工作者遇到了一个难题。对于每一段文字,其前缀在母串上的最大匹配长度是多少呢?
Input
第一行有两个整数,N和M,分别表示母串的长度和文字段的个数。
第二行是一个长度为N的字符串,所有字符都满足是E,S,W和N中的一个。
之后M行,每行有一个字符串,描述了一段带有玄武密码的文字。依然满足,所有字符都满足是E,S,W和N中的一个。
Output
输出有M行,对应M段文字。
每一行输出一个数,表示这一段文字的前缀与母串的最大匹配串长度。
Sample Input
7 3
SNNSSNS
NNSS
NNN
WSEE
Sample Output
4
2
0
HINT
对于100%的数据,N<=107,M<=105,每一段文字的长度<=100。
————————————————
版权声明:本文为CSDN博主「Aqua_blue」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Aqua_blue/java/article/details/78799508
#include<bits/stdc++.h>
using namespace std;
const int N=4e7+5,M=1e5+5;
string s,t;
int n,m,tr,trie[N][5],id[M],dep[N],pre[N],fail[N];
bool vis[N];
inline int id_(char a)
{
if(a=='E') return 1;
if(a=='S') return 2;
if(a=='W') return 3;
return 4;
}
inline void add(int num)
{
int p=0;
for(int i=0;i<s.length();++i)
{
int &x=trie[p][id_(s[i])];
if(!x) x=++tr,pre[x]=p,dep[x]=dep[p]+1;
p=x;
}
id[num]=p;
}
void getfail()
{
deque<int>q;
for(int i=1;i<=4;++i)
if(trie[0][i]) q.push_back(trie[0][i]);
while(!q.empty())
{
int u=q.front();
q.pop_front();
for(int i=1;i<=4;++i)
if(trie[u][i])
{
fail[trie[u][i]]=trie[fail[u]][i];
q.push_back(trie[u][i]);
}
else trie[u][i]=trie[fail[u]][i];
}
}
void query()
{
int j,p=0;
for(int i=0;i<t.length();++i)
{
j=p=trie[p][id_(t[i])];
while(j&&!vis[j]) vis[j]=1,j=fail[j];
}
}
int get(int x)
{
if(vis[x]||!x) return dep[x];
return get(pre[x]);
}
int main()
{
scanf("%d %d",&n,&m);
cin>>t;
for(int i=1;i<=m;++i)
{
cin>>s;
add(i);
}
getfail();
query();
for(int i=1;i<=m;++i) printf("%d\n",get(id[i]));
}
删除特定字串
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
string s,t;
int n,m,tr,ta,ans[N],id[N],trie[N][32],fail[N],end[N];
inline void add()
{
int p=0;
for(int i=0;i<s.length();++i)
{
int &x=trie[p][s[i]-'a'];
if(!x) x=++tr;
p=x;
}
end[p]=s.length();
}
void getfail()
{
deque<int>q;
for(int i=0;i<26;++i)
if(trie[0][i]) q.push_back(trie[0][i]);
while(!q.empty())
{
int u=q.front();
q.pop_front();
for(int i=0;i<26;++i)
if(trie[u][i])
{
fail[trie[u][i]]=trie[fail[u]][i];
q.push_back(trie[u][i]);
}
else trie[u][i]=trie[fail[u]][i];
}
}
void query()
{
int j,p=0;
for(int i=0;i<t.length();++i)
{
ans[++ta]=i;
id[i]=j=p=trie[p][t[i]-'a'];
if(end[j]) ta-=end[j],p=id[ans[ta]];
}
for(int i=1;i<=ta;++i) printf("%c",t[ans[i]]);
}
int main()
{
cin>>t;
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
cin>>s;
add();
}
getfail();
query();
}
最短母串
#include<bits/stdc++.h>
#define ts cout<<"ok"<<endl
#define ll long long
#define hh puts("")
using namespace std;
int n,f[5005][13],max_status,top;
string s[105],g[5005][13],ans[105];
inline int read(){
int ret=0,ff=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') ff=-ff;ch=getchar();}
while(isdigit(ch)){ret=(ret<<3)+(ret<<1)+(ch^48);ch=getchar();}
return ret*ff;
}
inline int check(int x,int y){
if(s[x].find(s[y])!=s[x].npos) return -1;//x包含y
for(int i=0;i<s[x].size();i++){//枚举j从哪一位开始与k重叠
bool fl=1;
for(int j=i;j<s[x].size();j++){
if(s[y][j-i]!=s[x][j]){
fl=0;
break;
}
}
if(fl) return s[x].size()-i;//返回重叠部分长
}
return 0;
}
signed main(){
n=read();
for(int i=1;i<=n;i++) cin>>s[i];
memset(f,0x3f,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++) f[1<<(i-1)][i]=s[i].length(),g[1<<(i-1)][i]=s[i];
max_status=(1<<n)-1;
for(int i=0;i<=max_status;i++){
for(int j=1;j<=n;j++){//以j结尾
if(i&(1<<(j-1))){
for(int k=1;k<=n;k++){//后面接k
if(!(i&(1<<(k-1)))){
int to=i|(1<<(k-1));
int chong=check(j,k);//重叠部分长度
if(chong==-1){//j包含k
if(f[i][j]<f[to][j]||(f[i][j]==f[to][j]&&g[to][j]>g[i][j])){
f[to][j]=f[i][j];
g[to][j]=g[i][j];
}
}
else{
string t="";
for(int p=chong;p<s[k].size();p++) t+=s[k][p];//t是j接上k后多出来的一段字符
t=g[i][j]+t;
if((f[i][j]+s[k].size()-chong<f[to][k])||
(f[i][j]+s[k].size()-chong==f[to][k]&&g[to][k]>t)){
f[to][k]=f[i][j]+s[k].size()-chong;
g[to][k]=t;
}
}
}
}
}
}
}
int minn=1e9;
for(int i=1;i<=n;i++){
if(f[max_status][i]<minn){
minn=f[max_status][i];
top=1;
ans[top]=g[max_status][i];
}
else if(f[max_status][i]==minn) ans[++top]=g[max_status][i];
}
sort(ans+1,ans+top+1);//排序找字典序最小
cout<<ans[1];
return 0;
}
病毒
Description
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。
任务:
请写一个程序:
l 读入病毒代码;
l 判断是否存在一个无限长的安全代码;
l 将结果输出
Input
第一行包括一个整数n,表示病毒代码段的数目。以下的n行每一行都包括一个非空的01字符串——就是一个病毒代码段。所有病毒代码段的总长度不超过30000。
Output
你应在在文本文件WIN.OUT的第一行输出一个单词:
l TAK——假如存在这样的代码;
l NIE——如果不存在。
Sample Input
3
01
11
00000
Sample Output
NIE
trie图上找到一条可以返回到不是end点的点的路。
#include<bits/stdc++.h>
using namespace std;
const int N=9e5+5;
string s;
int t,n,m,tr,trie[N][30],fail[N];
bool end[N],vis[N],siv[N];
void add()
{
int p=0;
for(int i=0;i<s.length();++i)
{
if(!trie[p][s[i]-'0']) trie[p][s[i]-'0']=++tr;
p=trie[p][s[i]-'0'];
}
end[p]=1;
}
void getfail()
{
deque<int>q;
for(int i=0;i<2;++i)
if(trie[0][i])
q.push_back(trie[0][i]);
while(!q.empty())
{
int u=q.front();
q.pop_front();
for(int i=0;i<2;++i)
if(trie[u][i])
{
int x=trie[u][i];
fail[x]=trie[fail[u]][i];
//结束关系的传递!!!!!
end[x]|=end[fail[x]];
q.push_back(x);
}
else trie[u][i]=trie[fail[u]][i];
}
}
bool dfs(int x)
{
siv[x]=vis[x]=1;
for(int i=0;i<2;++i)
{
int y=trie[x][i];
if(vis[y]) return 1;
if(siv[y]||end[y]) continue;
if(dfs(y)) return 1;
}
// if(trie[x][0]&&dfs(trie[x][0])) return 1;
// if(trie[x][1]&&dfs(trie[x][1])) return 1;
vis[x]=0;
return 0;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
cin>>s;
add();
}
getfail();
if(dfs(0)) cout<<"TAK";
else cout<<"NIE";
}
[BZOJ1030]文本生成器
weixin_30782293 2018-08-17 09:10:00 33 收藏
版权
Description
JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,
他们现在使用的是GW文本生成器v6版。该软件可以随机生成一些文章―――总是生成一篇长度固定且完全随机的文
章—— 也就是说,生成的文章中每个字节都是完全随机的。如果一篇文章中至少包含使用者们了解的一个单词,
那么我们说这篇文章是可读的(我们称文章a包含单词b,当且仅当单词b是文章a的子串)。但是,即使按照这样的
标准,使用者现在使用的GW文本生成器v6版所生成的文章也是几乎完全不可读的?。ZYX需要指出GW文本生成器 v6
生成的所有文本中可读文本的数量,以便能够成功获得v7更新版。你能帮助他吗?
Input
输入文件的第一行包含两个正整数,分别是使用者了解的单词总数N (<= 60),GW文本生成器 v6生成的文本固
定长度M;以下N行,每一行包含一个使用者了解的单词。这里所有单词及文本的长度不会超过100,并且只可能包
含英文大写字母A..Z
Output
一个整数,表示可能的文章总数。只需要知道结果模10007的值。
Sample Input
2 2
A
B
Sample Output
100
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,mod=10007;
string s;
int n,m,tr=0,ans,trie[N][30],fail[N],f[101][N];
bool end[N];
void add()
{
int p=0;
for(int i=0;i<s.length();++i)
{
if(!trie[p][s[i]-'A']) trie[p][s[i]-'A']=++tr;
p=trie[p][s[i]-'A'];
}
end[p]=1;
}
void getfail()
{
deque<int>q;
for(int i=0;i<26;++i)
if(trie[0][i])
q.push_back(trie[0][i]);
while(!q.empty())
{
int u=q.front();
q.pop_front();
for(int i=0;i<26;++i)
if(trie[u][i])
{
int x=trie[u][i];
fail[x]=trie[fail[u]][i];
end[x]|=end[fail[x]];
q.push_back(x);
}
else trie[u][i]=trie[fail[u]][i];
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i)
{
cin>>s;
add();
}
getfail();
f[0][0]=1;
for(int i=1;i<=m;++i)
for(int j=0;j<=tr;++j)
for(int k=0;k<26;++k)
if(!end[trie[j][k]])
f[i][trie[j][k]]=(f[i][trie[j][k]]+f[i-1][j])%mod;
int sum=0;ans=1;
for(int i=1;i<=m;++i) ans=(ans*26)%mod;
for(int i=0;i<=tr;++i) sum=(sum+f[m][i])%mod;
printf("%d",(ans-sum+mod)%mod);
}
Manacer算法(最大回文子串)
[国家集训队]最长双回文串
顺序和逆序读起来完全一样的串叫做回文串。比如acbca是回文串,而abc不是(abc的顺序为abc,逆序为cba,不相同)。
输入长度为nn的串SS,求SS的最长双回文子串TT,即可将TT分为两部分XX,YY,(|X|,|Y|≥1∣X∣,∣Y∣≥1)且XX和YY都是回文串。
输入格式
一行由小写英文字母组成的字符串SS。
输出格式
一行一个整数,表示最长双回文子串的长度。
输入输出样例
输入 #1 复制
baacaabbacabb
输出 #1 复制
12
说明/提示
【样例说明】
从第二个字符开始的字符串aacaabbacabb可分为aacaa与bbacabb两部分,且两者都是回文串。
对于100%的数据,2≤|S|≤10^52≤∣S∣≤10
5
#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
const int N=2e6+5;
string t,s;
int n,k,ans,len[N],l[N],r[N];
ull sl[N];
int main()
{
cin>>t;
s="~#";
for(int i=0;i<t.length();++i) s+=t[i],s+='#';
n=s.length();
s+='\0';
ans=0;
for(int i=1,mid=0,rr=0;i<=n;++i)
{
if(i<=rr) len[i]=min(len[mid*2-i],rr-i+1);
else len[i]=1;
while(s[i-len[i]]==s[i+len[i]]) ++len[i];
if(i+len[i]-1>rr) rr=i+len[i]-1,mid=i;
l[i-len[i]+1]=max(l[i-len[i]+1],len[i]-1);
r[i+len[i]-1]=max(r[i+len[i]-1],len[i]-1);
}
for(int i=1;i<=n;i+=2)
l[i]=max(l[i],l[i-2]-2);
for(int i=n;i;i-=2)
r[i]=max(r[i],r[i+2]-2);
for(int i=1;i<=n;i+=2)
if(l[i]&&r[i]) ans=max(ans,l[i]+r[i]);
printf("%d",ans);
}