字符串板子代码暂存

jiangly版本的Manachar

std::vector<int> manacher(std::string s) {
    std::string t = "#";
    for (auto c : s) {
        t += c;
        t += '#';
    }
    int n = t.size();
    std::vector<int> r(n);
    for (int i = 0, j = 0; i < n; i++) {
        if (2 * j - i >= 0 && j + r[j] > i) {
            r[i] = std::min(r[2 * j - i], j + r[j] - i);
        }
        while (i - r[i] >= 0 && i + r[i] < n && t[i - r[i]] == t[i + r[i]]) {
            r[i] += 1;
        }
        if (i + r[i] > j + r[j]) {
            j = i;
        }
    }
    return r;
}
 

Manacher

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2.2e7+7;
int d[N];
void manacher(string s)
{
	int l=0,r=0;//l,r代表右端点最靠右的回文串区间
	d[0]=1;//d代表回文半径
	for(int i=1; i<s.size(); i++)
	{
		int k=(i>r)?1:min(d[l+r-i],r-i+1);//找i的对称位置,利用之前的信息+暴力完成求解
		while(s[i-k]==s[i+k]) k++;
		d[i]=k;
		k--;
		if(i+k>r)//更新最右端的回文串区间
		{
			l=i-k; r=i+k;
		}
	}
}
void O_o()
{
	string t,s="";
	cin>>t;
	s+="&";//插入特殊字符,防止越界 
	for(auto c:t)
	{
		s+="#";//中间插入#,把偶数回文串转化为奇数回文串
		s+=c;
	}
	s+="#^";
	manacher(s);
	int ans=0;
	for(int i=0; i<s.size(); i++) ans=max(ans,d[i]-1);//去掉#后的回文串长度 
	cout<<ans;
}
signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cout<<fixed<<setprecision(2);
	int T=1;
//	cin>>T;
	while(T--)
	{
		O_o();
	}
}

ACAM

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+77;
int id[N];
struct AC_automation
{
	//不难发现,跳fail相当于舍弃一段前缀,保留后缀,这和KMP中的next是一样的
	//所有的fail指针会构成一棵树,所以我们查询的时候可以不跳fail,用拓扑排序解决出现次数问题。 
	int tr[N][27],fail[N],ans[N],cnt=1,rd[N];
	
	void insert(string s,int i)
	{
		int u=1;
		for(auto c:s)
		{
			int v=c-'a';
			if(!tr[u][v]) tr[u][v]=++cnt;
			u=tr[u][v];
		}
		//可以开一个数组记录这个节点有多少个模式串 
		id[i]=u;
	}
	
	void build()
	{
		//用bfs建AC自动机 
		
		queue<int> q;
		for(int i=0; i<26; i++) tr[0][i]=1;
		q.push(1);
		fail[1]=0;
		while(!q.empty())
		{
			int u=q.front(); q.pop();
			//此时的节点上面的fail都已经求出来了 
			for(int i=0; i<26; i++)
			{
				if(tr[u][i])
				{
					fail[tr[u][i]]=tr[fail[u]][i];//这和不断跳fail,找到拥有i的节点是等价的 
					rd[tr[fail[u]][i]]++;//记录fail树入度,拓扑排序用 
					q.push(tr[u][i]);
				}
				else tr[u][i]=tr[fail[u]][i];
			}
		}
	}
	
	//正常版query 
//	void query(string s)
//	{
//		int u=1;
//		for(auto t:s)
//		{
//			u=tr[u][t-'a'];
//			for(int v=u; v; v=fail[v])
//			{
//				ans[v]++;
//			}
//		}
//	}
	
	//加强版query
	void ask(string s)
	{
		int u=1;
		for(auto t:s)
		{
			u=tr[u][t-'a'];
			ans[u]++; 
		}
	}  
	void query(string s)//fail树上dp(拓扑排序) 
	{
		ask(s);
		queue<int> q;
		for(int i=1; i<=cnt; i++) if(!rd[i]) q.push(i);
		while(!q.empty())
		{
			int v=q.front(); q.pop();
			int u=fail[v];
			ans[u]+=ans[v];
			rd[u]--;
			if(!rd[u]) q.push(u);
		}
	}
}AC;

void O_o()
{
	int n;
	cin>>n;
	for(int i=1; i<=n; i++)
	{
		string s;
		cin>>s;
		AC.insert(s,i);
	}
	AC.build();
	string s;
	cin>>s;
	AC.query(s);
	for(int i=1; i<=n; i++) cout<<AC.ans[id[i]]<<"\n";
}
signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cout<<fixed<<setprecision(2);
	int T=1;
//	cin>>T;
	while(T--)
	{
		O_o();
	}
}

SAM(子串出现次数)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e6+77;
int cnt,dp[N],ls[N];
ll ans=0;
struct SAM
{
	int fa,len,nxt[27];
}sam[N];
struct E
{
	int to,next;
}e[N];
void add(int u,int v)
{
	e[++cnt].to=v; e[cnt].next=ls[u]; ls[u]=cnt;
}
int last=1,tot=1;
void insert(int ch)
{
	int cur=++tot,p; //新建节点cur
	sam[cur].len=sam[last].len+1; //cur的endpos最长串长度为last.len+1 
	dp[cur]=1; //后面dp子串出现次数要算自身,所以这里直接给他dp[cur]=1
	for(p=last; (!sam[p].nxt[ch])&&p; p=sam[p].fa) //从last往上跳fa,找到p.nxt[ch]!=0的p,这样p可以通过ch转移过去,证明endpos不一样了 
	{
		sam[p].nxt[ch]=cur; //相当于是把沿途的后缀补上ch 
	}
	int q=sam[p].nxt[ch];
	if(!q) //没找到
	{
		sam[cur].fa=1;
	}
	else if(sam[q].len==sam[p].len+1) //连续转移,endpos最长串就是我们要的
	{
		sam[q].len=sam[p].len+1;
		sam[cur].fa=q;
	}
	else if(sam[q].len!=sam[p].len+1) //非连续转移,endpos最长串不是我们要的,我们要的是它的一个后缀
	{
		int r=++tot; 
		sam[r]=sam[q]; //把q克隆一份 
		sam[r].len=sam[p].len+1; //我们想要的后缀的长度
		for(; sam[p].nxt[ch]==q; p=sam[p].fa) 
			sam[p].nxt[ch]=r; //把全部指向p的点改成r
		sam[cur].fa=sam[q].fa=r; //根据fa的定义“串最长的,不属于同一endpos的节点”连fa 
	}
	last=cur;
}
void dfs(int u)
{
	for(int i=ls[u]; i; i=e[i].next)
	{
		int v=e[i].to;
		dfs(v);
		dp[u]+=dp[v];
	}
	if(dp[u]!=1) ans=max(ans,1ll*dp[u]*sam[u].len);
}
void O_o()
{
	string s;
	cin>>s;
	for(int i=0; i<s.size(); i++) insert(s[i]-'a');
	for(int i=2; i<=tot; i++) if(sam[i].fa!=0)
	{
		add(sam[i].fa,i);
	}
	dfs(1);
	cout<<ans;
}
signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cout<<fixed<<setprecision(2);
	int T=1;
//	cin>>T;
	while(T--)
	{
		O_o();
	}
}

SAM封装版(LCP)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+77;
int a[N];
struct SAM//封装版本 
{
	int tr[N][27],fa[N],len[N];
	int tot=1,last=1;
	void clone(int r,int q)
    {
        for(int i=0; i<26; i++) tr[r][i]=tr[q][i];
        fa[r]=fa[q];
    }
	void insert(int ch)
	{
		int cur=++tot,p;//新建节点cur 
		len[cur]=len[last]+1;//cur的endpos最长串长度为last.len+1; 
		for(p=last; (!tr[p][ch])&&p; p=fa[p]) //从last往上跳fa,找到p.nxt[ch]!=0的p,这样p可以通过ch转移过去,证明endpos不一样了 
		{
			tr[p][ch]=cur; //相当于是把沿途的后缀补上ch 
		}
        int q=tr[p][ch];
        if(!q)//没找到
        {
            fa[cur]=1;
        }
        else if(len[q]==len[p]+1)//连续转移,endpos最长串就是我们要的
        {
            fa[cur]=q;
        }
        else //非连续转移,endpos最长串不是我们要的,我们要的是它的一个后缀
        {
            int r=++tot; 
            clone(r,q); //把q克隆一份 
            len[r]=len[p]+1; //我们想要的后缀的长度
            for(; tr[p][ch]==q; p=fa[p]) 
                tr[p][ch]=r; //把全部指向p的点改成r
            fa[cur]=fa[q]=r; //根据fa的定义“串最长的,不属于同一endpos的节点”连fa 
        }
		last=cur;
    }
    int query(string s)//求lcp
    {
        int u=1,ass=0,ans=0;
        for(auto c:s)
        {
            int v=c-'a';
            if(tr[u][v])
            {
                ass++; u=tr[u][v];
            }
            else
            {
                for(; u&&(!tr[u][v]); u=fa[u]);
                if(u)
                {
                    ass=len[u]+1;
                    u=tr[u][v];
                }
                else
                {
                    u=1;
                    ass=0;
                }
            }
            ans=max(ans,ass);
        }
        return ans;
    }
}SAM;
void O_o()
{
	string s;
	cin>>s;
	for(auto c:s) SAM.insert(c-'a');
    cin>>s;
    cout<<SAM.query(s);
}
signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cout<<fixed<<setprecision(2);
	int T=1;
//	cin>>T;
	while(T--)
	{
		O_o();
	}
}

SA

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+77;
int rk[N],sa[N],cnt[N],id[N],n,key[N],oldrk[N];
bool cmp(int x,int y,int w)
{
	return (oldrk[x]==oldrk[y])&&(oldrk[x+w]==oldrk[y+w]);
}
void getSA(string s)
{
	int m=127,p=0; //字符集大小
	//按后缀第一个字母计数排序 
	for(int i=1; i<=n; i++) cnt[rk[i]=s[i]]++;
	for(int i=1; i<=m; i++) cnt[i]+=cnt[i-1];
	for(int i=n; i>=1; i--) sa[cnt[rk[i]]--]=i;
	
	
	for(int w=1; ; w<<=1,m=p)
	{
		//第二关键字排序的本质:把sa[i]+w超出n的部分放到最前面,然后把剩下的依原顺序放入
		p=0;
		for(int i=n; i>n-w; i--) id[++p]=i;
		for(int i=1; i<=n; i++)
    		if(sa[i]>w) id[++p]=sa[i]-w;
    		
    	//第一关键字计数排序 
    	memset(cnt,0,sizeof(cnt));
    	for(int i=1; i<=n; i++) cnt[key[i]=rk[id[i]]]++;
    	for(int i=1; i<=m; i++) cnt[i]+=cnt[i-1];
    	for(int i=n; i>=1; i--) sa[cnt[key[i]]--]=id[i];
    	
    	//求出rk 
    	p=0;
    	for(int i=1; i<=n; i++) oldrk[i]=rk[i];
    	for(int i=1; i<=n; i++)
    	{
    		if(!cmp(sa[i],sa[i-1],w)) p++;
    		rk[sa[i]]=p;
		}
		if(p==n) break;
	}
	
}
void getHeight()
{
	//height[i]=lcp(sa[i],sa[i-1])
	//引理:height[rk[i]]>=height[rk[i]-1]-1
	//由此我们就可以暴力2*n做
	int tot=0;
	for(int i=1; i<=n; i++)
	{
		if(tot) tot--;//引理
		while(s[i+tot]==s[sa[rk[i]-1]+tot]) ++tot;//求lcp 
		height[rk[i]]=tot;
	}
	
	/*
	应用:
	lcp(sa[i],sa[j])=min{height[i]...height[j]} ,便可以用RMQ求解lcp
	
	比较两个子串的大小关系A=[a..b],B=[c..d]:
	若 lcp(a,c)>=min(|A|,|B|},则长度长的排名大 
	否则直接看rk[a]和rk[c]的大小 
	
	求不同的子串个数:
	子串就是后缀的前缀,总数为n*(n+1)/2
	考虑按后缀排序后顺序枚举后缀,每次新增的子串其实就是去掉他们lcp的结果
	所以ans=n*(n+1)/2-height[1..n] 
	 
	*/
}
void O_o()
{
	string s;
	cin>>s;
	n=s.size();
	s=' '+s;
	getSA(s);
	for(int i=1; i<=n; i++) cout<<sa[i]<<" ";
}
signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cout<<fixed<<setprecision(2);
	int T=1;
//	cin>>T;
	while(T--)
	{
		O_o();
	}
}

PAM(子串出现次数)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+77,inf=2e9;
struct PAM
{
	int siz/*自动机大小*/,last/*前一个节点*/,tot/*字符串当前长度*/;
	int cnt[N]/*节点出现次数*/,ch[N][26]/*儿子*/,len[N],fail[N];
	string s;
	
	int add(int l)//新建节点,长度为l
	{
		siz++;
//		memset(ch[siz],0,sizeof(ch[siz]));
		len[siz]=l;
		fail[siz]=cnt[siz]=0;
		return siz;
	}
	
	void init()//初始化
	{
		siz=-1;
		last=tot=0;
		s="$";
		add(0);
		add(-1);//奇根的长度为-1,永不失配
		fail[0]=1;//偶根的fail指向奇根
	}
	
	int getfail(int x)//找回文后缀
	{
		while(s[tot-len[x]-1]!=s[tot]) 
			x=fail[x];
		//找到一个节点满足其所对应的回文串的上一个字符和与待添加的字符相同
		return x;
	}
	
	void insert(char c)//建树,类似于SAM 
	{
		s+=c;
		tot++;
		int now=getfail(last);
		if(!ch[now][c-'a'])//节点不存在
		{
			int x=add(len[now]+2);
			fail[x]=ch[getfail(fail[now])][c-'a'];
			ch[now][c-'a']=x;
		}
		last=ch[now][c-'a'];
		cnt[last]++;
	}
}pam;
void solve()//类似于SAM,可以在fail树上dp
{
	int ans=0;
	for(int i=pam.siz; i>=0; i--)
	{
		pam.cnt[pam.fail[i]]+=pam.cnt[i];
	}
	for(int i=1; i<=pam.siz; i++)
	{
		ans=max(ans,pam.cnt[i]*pam.len[i]);
	}
	cout<<ans;
}
void O_o()
{
	string s;
	cin>>s;
	pam.init();
	for(auto c:s)
	{
		pam.insert(c);
	}
	solve();
}
signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cout<<fixed<<setprecision(2);
	int T=1;
//	cin>>T;
	while(T--)
	{
		O_o();
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值