• [练习记录]字符串


    CF932G Palindrome Partition

    考虑\(s_i,s_{k - i + 1}\) 两段。

    \(s_i\)的开始的位置为\(p\),长度为\(j\).

    那么有\(s[p] = s[n - p - j + 1]\)

    那么如果我们把串\(S\)转成\(S'\)

    \(S' = s[1]s[n]s[2][n - 2]...\)

    那么询问的实际上是把这个串划分为若干长度为偶数的回文串的方案数。

    那么考虑写出一个暴力\(dp\).

    \(f_i = \sum f_{j - 1}(s'[j,i]是偶回文串)\)

    如果暴力跳\(fail\)则其复杂度为\(O(n^2)\)

    假设对于某个位置,他对应的若干回文后缀,如果他们的长度大于\(len / 2\),则他们的长度等差。

    如果把这些回文后缀全看做一组,每更换一组长度就至少除\(2\),那么只会有\(log\)组,考虑我们是否能够一组组转移。

    \(g_x\)表示一组的和。

    那么我们在构造的时候顺便求出其等差的回文后缀在哪个节点结束。

    CF932G Palindrome Partition
    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define ll long long
    #define RG register
    #define MAX 1000100
    #define MOD 1000000007
    inline int read()
    {
        RG int x=0,t=1;RG char ch=getchar();
        while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
        if(ch=='-')t=-1,ch=getchar();
        while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
        return x*t;
    }
    char ch[MAX],s[MAX];
    int n,anc[MAX],diff[MAX];
    int ans[MAX],f[MAX];
    struct Palindromic_Tree
    {
    	struct Node
    	{
    		int son[26];
    		int ff,len;
    	}t[MAX];
    	int last,tot;
    	void init()
    	{
    		t[tot=1].len=-1;
    		t[0].ff=t[1].ff=1;
    		anc[0]=1;
    	}
    	void extend(int c,int n,char *s)
    	{
    		int p=last;
    		while(s[n-t[p].len-1]!=s[n])p=t[p].ff;
    		if(!t[p].son[c])
    		{
    			int v=++tot,k=t[p].ff;
    			t[v].len=t[p].len+2;
    			while(s[n-t[k].len-1]!=s[n])k=t[k].ff;
    			t[v].ff=t[k].son[c];
    			t[p].son[c]=v;
    			diff[v]=t[v].len-t[t[v].ff].len;
    			anc[v]=(diff[v]==diff[t[v].ff])?anc[t[v].ff]:t[v].ff;
    		}
    		last=t[p].son[c];
    	}
    }PT;
    int main()
    {
    	PT.init();
    	scanf("%s",ch+1);
    	n=strlen(ch+1);
    	if(n&1){puts("0");return 0;}
    	for(int i=1;i<=n;i+=2)s[i]=ch[(i+1)/2];
    	reverse(&ch[1],&ch[n+1]);
    	for(int i=2;i<=n;i+=2)s[i]=ch[(i+1)/2];
    	ans[0]=1;
    	for(int i=1;i<=n;++i)
    	{
    		PT.extend(s[i]-97,i,s);
    		for(int k=PT.last;k;k=anc[k])
    		{
    			f[k]=ans[i-PT.t[anc[k]].len-diff[k]];
    			if(anc[k]!=PT.t[k].ff)
    				f[k]=(f[k]+f[PT.t[k].ff])%MOD;
    			if(!(i&1))ans[i]=(ans[i]+f[k])%MOD;
    		}
    	}
    	printf("%d\n",ans[n]);
    	return 0;
    }
    
    

    P4287 [SHOI2011]双倍回文

    考虑每个双倍回文串,都是长度为\(4\)的,有一个回文后缀为\(len / 2\)

    考虑直接对每个\(i\)维护\(f_i\),表示小于等于一半串长的最深的节点。

    P4287 [SHOI2011]双倍回文
    #include <cstdio>
    #include <cstring>
    const int N=5e5+10;
    int ch[N][26],fail[N],f[N],len[N],tot,n;
    char s[N];
    int getfail(int now,int p)
    {
        while(s[p]!=s[p-len[now]-1]) now=fail[now];
        return now;
    }
    void PAM()
    {
        len[fail[0]=++tot]=-1;
        scanf("%d%s",&n,s+1);
        for(int now,las=0,i=1;i<=n;i++)
        {
            int cur=getfail(las,i),c=s[i]-'a';
            if(!ch[cur][c])
            {
                fail[now=++tot]=ch[getfail(fail[cur],i)][c];
                len[now]=len[cur]+2;
                ch[cur][c]=now;
                if(len[fail[now]]<=len[now]>>1) f[now]=fail[now];
                else
                {
                    int p=f[cur];
                    while((len[p]+2>len[now]>>1)||(s[i]!=s[i-len[p]-1])) p=fail[p];
                    f[now]=ch[p][c];
                }
            }
            las=ch[cur][c];
        }
    }
    int main()
    {
        PAM();int ans=0;
        for(int i=2;i<=tot;i++)
            if(len[i]%4==0&&len[f[i]]==len[i]>>1)
                ans=ans>len[i]?ans:len[i];
        printf("%d\n",ans);
        return 0;
    }
    

    Petrozavodsk Winter Training Camp 2016 Day4 D Deep Purple

    考虑到实际上求的是\(S_{l,r}\)的最大\(border\).

    考虑对于反串建立\(SAM\)

    实际上是求\(\max p,LCS(S_{1,r},S_{1,p}) > p - l\) ,考虑变换不等式\(LCS(S_{1,r},S_{1,p}) + l > p\)

    那么实际上\(LCS(S_{1,r},S_{1,p}) = dep(LCA(r,p))\)

    我们考虑倒序着扫描线,每遇到一个询问就加入其,对于单点对点集\(LCA\)问题的一个经典解法即把点集中到根的路径全标记然后对单点到根路径上的查询,由于我们要对\(dep + l\)这个量维护,但由于区间加无法对\(dep\)独立的问题操作。
    我们试图思考更加本质的性质:\(LCA\)在树剖上只有三种情况:\(u,v\)以及\(v\)\(u\)到根对应的一个重区间的最低点。

    那么第三种,我们只要对\(log\)个重区间的最低点处理即可。

    第一种因为长度限制,我们倒序扫描线时一定是长度减小,在SAM的后缀树上父亲的长度一定小于儿子,那么这意味着只有我们扫到的那个单点可以作为\(LCA\),那我们只要在其所在的重链底部找到最大的\(l\),并加上\(dep_x\)即可。

    第二种,即从\(x\)一路查找区间底部即可。

    实际上其是把\(O(nlog) - O(log)\)问题,转化为了\(O(log^2) - O(log^2)\)

    找到了最大的询问后,即一路撤销其影响,因为有这步操作所以我们需要对树上的点开一个\(set\)来处理他存的点。

    CF700E Cool Slogans

    构建SAM,用线段树合并求出\(endpos\)的集合。

    然后考虑在后缀树上\(dp\)

    可转移条件为儿子的任意出现位置\(pos\),\([pos - len(x) + len(link_x),pos - 1]\)出现过一次父亲即可。

    CF700E Cool Slogans
    #include<bits/stdc++.h>
    #define ll long long
    
    const int N = 2e5 + 7;
    struct T {
    	int l, r;
    } t[N<<6|1];
    int rt[N<<1], f[N<<1], g[N<<1], tot, ans = 1;
    
    int insert(int l, int r, int x) {
    	int p = ++tot;
    	if (l == r) return p;
    	int mid = (l + r) >> 1;
    	if (x <= mid) t[p].l = insert(l, mid, x);
    	else t[p].r = insert(mid + 1, r, x);
    	return p;
    }
    
    int merge(int p, int q) {
    	if (!p || !q) return p | q;
    	int o = ++tot;
    	t[o].l = merge(t[p].l, t[q].l);
    	t[o].r = merge(t[p].r, t[q].r);
    	return o;
    }
    
    bool ask(int p, int l, int r, int L, int R) {
    	if (!p) return 0;
    	if (L <= l && R >= r) return 1;
    	int mid = (l + r) >> 1;
    	if (L <= mid && ask(t[p].l, l, mid, L, R)) return 1;
    	if (R > mid && ask(t[p].r, mid + 1, r, L, R)) return 1;
    	return 0;
    }
    
    struct SAM {
    	int n, l, ch[N<<1][26], len[N<<1], pa[N<<1], t;
    	char s[N];
    	int pos[N<<1];
    	inline void init() { pa[0] = -1; }
    	inline void add(int c, int o) {
    		int w = ++t, p = l;
    		len[w] = len[l] + 1, pos[w] = o;
    		while (~p && !ch[p][c]) ch[p][c] = w, p = pa[p];
    		if (!~p) return pa[l=w] = 0, void();
    		int q = ch[p][c];
    		if (len[p] + 1 == len[q]) return pa[l=w] = q, void();
    		int k = ++t;
    		pa[k] = pa[q], pos[k] = pos[q], memcpy(ch[k], ch[q], sizeof(ch[k]));
    		len[k] = len[p] + 1, pa[w] = pa[q] = k;
    		while (~p && ch[p][c] == q) ch[p][c] = k, p = pa[p];
    		l = w;
    	}
    	inline void build() {
    		init();
    		for (int i = 1; i <= n; i++) add(s[i] - 'a', i);
    	}
    	int b[N<<1], c[N];
    	inline void sort() {
    		for (int i = 1; i <= t; i++) ++c[len[i]];
    		for (int i = 1; i <= n; i++) c[i] += c[i-1];
    		for (int i = 1; i <= t; i++) b[c[len[i]]--] = i;
    	}
    	inline void work() {
    		for (int i = 1, p = 0; i <= n; i++)
    			p = ch[p][s[i]-'a'], rt[p] = insert(1, n, i);
    		for (int i = t; i; i--)
    			rt[pa[b[i]]] = merge(rt[pa[b[i]]], rt[b[i]]);
    		for (int i = 1; i <= t; i++) {
    			int x = b[i];
    			if (!pa[x]) {
    				f[x] = 1, g[x] = x;
    				continue;
    			}
    			if (ask(rt[g[pa[x]]], 1, n, pos[x] - len[x] + len[g[pa[x]]], pos[x] - 1))
    				f[x] = f[pa[x]] + 1, g[x] = x;
    			else f[x] = f[pa[x]], g[x] = g[pa[x]];
    			ans = std::max(ans, f[x]);
    		}
    	}
    } sam;
    
    int main() {
    	scanf("%d",&sam.n);
    	scanf("%s",sam.s + 1);
    	sam.build();
    	sam.sort(), sam.work();
    	std::cout<<ans<<std::endl;
    	return 0;
    }
    

    CF1073G Yet Another LCP Problem

    反串建后缀树。

    然后发现\(LCP(suf_i,suf_j) = len(LCA(ed_i,ed_j))\)

    点击查看代码
    //晦暗的宇宙,我们找不到光,看不见尽头,但我们永远都不会被黑色打倒。
    #include<bits/stdc++.h>
    #define ll long long
    #define N 400005
    
    int n,q;
    
    char a[N];
    
    using std::vector;
    
    vector<int>A[N * 2];//tree
    
    namespace SAM{
    	struct P{
    		int link,len,ch[28];
    	}a[N * 2];
    	int lst = 1,cnt = 1,root = 1;
    	inline int insert(int c){
    		int cur = ++cnt,p = lst;
    		lst = cur;
    		a[cur].len = a[p].len + 1;
    		while(p && !a[p].ch[c])
    		a[p].ch[c] = cur,p = a[p].link;
    		if(!p)return a[cur].link = 1,cur;
    		int q = a[p].ch[c];
    		if(a[q].len == a[p].len + 1)
    		return a[cur].link = q,cur;
    		int t = ++cnt;
    		a[t] = a[q];
    		a[q].link = a[cur].link = t;a[t].len = a[p].len + 1;
    		while(p && a[p].ch[c] == q)a[p].ch[c] = t,p = a[p].link;
    		return cur;
    	}
    	inline void link(){
    		for(int i = 2;i <= cnt;++i)
    		std::cout<<a[i].link<<" "<<a[i].len<<"\n",A[a[i].link].push_back(i);
    	}
    }
    
    int ed[N];
    
    int siz[N * 2],son[N * 2];
    int fa[N * 2]; 
    
    inline void dfs(int u,int f){
    	siz[u] = 1;
    	fa[u] = f;
    	for(int v : A[u]){
    		dfs(v,u);
    		siz[u] += siz[v];
    		if(siz[v] > siz[son[u]])
    		son[u] = v;
    	}
    }
    
    int top[N * 2];
    int dfn[N * 2],cnt;
    int w[N * 2];
    
    inline void dfs2(int u,int t){
    	top[u] = t;
    	dfn[u] = ++ cnt;
    //	std::cout<<u<<" "<<t<<" "<<dfn[u]<<"\n";
    	w[dfn[u]] = SAM::a[u].len - SAM::a[SAM::a[u].link].len;
    	if(son[u])dfs2(son[u],t);
    	std::reverse(A[u].begin(),A[u].end());
    	for(int v : A[u]){
    		if(v == son[u])continue;
    		dfs2(v,v);
    	} 
    }
    
    //树剖
    
    struct P{
    	int sumw;
    	ll sum;
    	int tag;
    	P(){sumw = sum = tag = 0;}
    }T[N * 4];
    
    #define ls(x) (x << 1)
    #define rs(x) (x << 1 | 1)
    #define mid ((l + r) >> 1)
    #define tag(x) T[x].tag
    #define sw(x) T[x].sumw
    #define s(x) T[x].sum
    
    inline void up(int u){sw(u) = sw(ls(u)) + sw(rs(u)),s(u) = s(ls(u)) + s(rs(u));}
    
    inline void build(int u,int l,int r){
    	if(l == r){
    		sw(u) = w[l];
    		return ;
    	} 
    	build(ls(u),l,mid);
    	build(rs(u),mid + 1,r);
    	up(u);
    }
    
    inline void down(int u){
    	if(tag(u)){
    		int k = tag(u);
    		tag(ls(u)) += k;
    		s(ls(u)) += sw(ls(u)) * k;
    		tag(rs(u)) += k;
    		s(rs(u)) += sw(rs(u)) * k;				
    		tag(u) = 0;
    	}
    }
    
    inline void change(int u,int l,int r,int tl,int tr,int k){
    	if(tl == 0)return ;
    //		std::cout<<l<<" "<<r<<" "<<tl<<" "<<tr<<" "<<k<<" "<<s(u)<<"\n";				
    	if(tl <= l && r <= tr){
    		tag(u) += k;
    		s(u) += sw(u) * k;
    //		std::cout<<l<<" "<<r<<" "<<tl<<" "<<tr<<" "<<k<<" "<<s(u)<<"\n";			
    		return ;
    	}
    	down(u);
    	if(tl <= mid)
    	change(ls(u),l,mid,tl,tr,k);
    	if(tr > mid)
    	change(rs(u),mid + 1,r,tl,tr,k);
    	up(u);
    //	std::cout<<l<<" "<<r<<" "<<tl<<" "<<tr<<" "<<k<<" "<<s(u)<<"\n";	
    }
    
    inline ll find(int u,int l,int r,int tl,int tr){
    	if(tl == 0)return 0;	
    	if(tl <= l && r <= tr){
    		std::cout<<l<<" "<<r<<" "<<tl<<" "<<tr<<" "<<s(u)<<"\n";		
    		return s(u);
    	}
    	down(u);
    	ll res = 0;
    	if(tl <= mid)
    	res = res + find(ls(u),l,mid,tl,tr);
    	if(tr > mid)
    	res = res + find(rs(u),mid + 1,r,tl,tr);
    //		std::cout<<l<<" "<<r<<" "<<tl<<" "<<tr<<" "<<res<<"\n";			
    	return res;
    }
    
    //Seg 
    
    int ai[N],bi[N];
    
    ll ans = 0;
    
    int main(){
    	scanf("%d%d",&n,&q);
    	scanf("%s",a + 1);
    	for(int i = n;i >= 1;--i)
    	ed[i] = SAM::insert(a[i] - 'a');	
    	SAM::link();
    	dfs(1,0);
    	dfs2(1,1);
    	n = SAM::cnt;
    	for(int i = 1;i <= n;++i)
    	std::cout<<w[i]<<" "; 	
    	build(1,1,n);
    	while(q -- ){
    		int an,bn;
    		scanf("%d%d",&an,&bn);
    		for(int i = 1;i <= an;++i)
    		scanf("%d",&ai[i]);
    		for(int i = 1;i <= bn;++i)
    		scanf("%d",&bi[i]);
    		for(int i = 1;i <= bn;++i){
    			int u = ed[bi[i]];
    			while(u){change(1,1,n,dfn[top[u]],dfn[u],1),u = fa[top[u]];}
    		}
    		ans = 0;
    		for(int i = 1;i <= an;++i){
    			int u = ed[ai[i]];
    			while(u){ans += find(1,1,n,dfn[top[u]],dfn[u]),u = fa[top[u]];}
    		}
    		std::cout<<ans<<"\n";
    		for(int i = 1;i <= bn;++i){
    			int u = ed[bi[i]];
    			while(u){change(1,1,n,dfn[top[u]],dfn[u],-1),u = fa[top[u]];}
    		}		
    	}
    }
    

    CF954I Yet Another String Matching Problem

    考虑一个子串只要有一个位置对应不同我们就需要反转一次。

    直接枚举不同的对,然后就可以FFT匹配了。

    代码明天补上。

    [CTSC2010]珠宝商

    考虑先建出\(SAM\).

    一个简单的想法是一次\(O(n)\)直接搜索一颗子树,然后在\(SAM\)上跑,直接统计。

    一个不那么简单的想法是点分治,求出经过中点的\(a\to x \to b\)的路径,\(a \to x\)\([1,p]\)的后缀,\(x\to b\)\([p,n]\) 的次数乘起来。

    考虑二者实际上一样的。

    考虑相当于每次都在前缀加了一个字符,这个可以通过求出反串的后缀树求出,由于反串的后缀树等于反反 = 正串的\(parent\)树,那么我们只要顺带处理出字符转移即可。

    考虑后者的复杂度为\(O(nlog + nm)\)

    考虑使用根号分治,由于前者和子树大小有关,后者和计算次数有关,取\(\sqrt n\)可做到\(O((n + m)\sqrt n)\)

    点击查看代码
    #include<bits/stdc++.h>
    using namespace std;
    
    #define ll long long 
    
    void read(int &x) {
        x=0;int f=1;char ch=getchar();
        for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
        for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
    }
     
    void print(ll x) {
        if(x<0) putchar('-'),x=-x;
        if(!x) return ;print(x/10),putchar(x%10+48);
    }
    void write(ll x) {if(!x) putchar('0');else print(x);putchar('\n');}
    
    const int maxn = 2e5+10;
    const int inf = 1e9;
    
    ll ans;
    char s[maxn];
    int n,m,rt,siz,B,top;
    int head[maxn],tot,a[maxn];
    int vis[maxn],f[maxn],sz[maxn],tmp[maxn],t[maxn];
    struct edge{int to,nxt;}e[maxn<<1];
    
    void ins(int u,int v) {e[++tot]=(edge){v,head[u]},head[u]=tot;}
    
    struct Suffix_Automaton {
    	int cnt,qs,lstp;
    	int par[maxn],tr[maxn][26],ml[maxn],pos[maxn],sz[maxn];
    	int t[maxn],r[maxn],son[maxn][26],str[maxn],tag[maxn],rev[maxn];
    
    	void append(int x,int v) {
    		int p=lstp,np=++cnt;pos[np]=v,sz[np]=1,ml[np]=ml[p]+1,rev[v]=np;lstp=np;
    		for(;p&&tr[p][x]==0;p=par[p]) tr[p][x]=np;
    		if(!p) return par[np]=qs,void();
    		int q=tr[p][x];
    		if(ml[p]+1<ml[q]) {
    			int nq=++cnt;ml[nq]=ml[p]+1;
    			memcpy(tr[nq],tr[q],sizeof tr[nq]);
    			par[nq]=par[q],par[q]=par[np]=nq;
    			for(;p&&tr[p][x]==q;p=par[p]) tr[p][x]=nq;
    		} else par[np]=q;
    	}
    
    	void prepare(char *ss) {
    		lstp=qs=cnt=1;
    		for(int i=1;i<=m;i++) append(str[i]=ss[i]-'a',i);
    
    		for(int i=1;i<=cnt;i++) t[ml[i]]++;
    		for(int i=1;i<=m;i++) t[i]+=t[i-1];
    		for(int i=1;i<=cnt;i++) r[t[ml[i]]--]=i;
    
    		for(int i=cnt;i;i--) {
    			int p=r[i];
    			sz[par[p]]+=sz[p];
    			if(!pos[par[p]]) pos[par[p]]=pos[p];
    			son[par[p]][str[pos[p]-ml[par[p]]]]=p;
    		}
    	}
    
    	void mark(int x,int fa,int now,int len) {
    		if(len==ml[now]) now=son[now][a[x]];
    		else if(str[pos[now]-len]!=a[x]) now=0;
    		if(!now) return ;len++;tag[now]++;
    		for(int i=head[x];i;i=e[i].nxt)
    			if(e[i].to!=fa&&!vis[e[i].to]) mark(e[i].to,x,now,len);
    	}
    
    	void push() {for(int i=1;i<=cnt;i++) tag[r[i]]+=tag[par[r[i]]];}
    }sam1,sam2;
    
    void get_rt(int x,int fa) {
    	sz[x]=1,f[x]=0;
    	for(int i=head[x];i;i=e[i].nxt)
    		if(e[i].to!=fa&&!vis[e[i].to]) 
    			get_rt(e[i].to,x),sz[x]+=sz[e[i].to],f[x]=max(f[x],sz[e[i].to]);
    	f[x]=max(f[x],siz-sz[x]);
    	if(f[x]<f[rt]) rt=x;
    }
    
    void get_node(int x,int fa) {
    	t[++top]=x;
    	for(int i=head[x];i;i=e[i].nxt)
    		if(e[i].to!=fa&&!vis[e[i].to]) get_node(e[i].to,x);
    }
    
    void dfs(int x,int fa,int now) {
    	now=sam1.tr[now][a[x]];
    	if(!now) return ;
    	ans+=sam1.sz[now];
    	for(int i=head[x];i;i=e[i].nxt)
    		if(e[i].to!=fa&&!vis[e[i].to]) dfs(e[i].to,x,now);
    }
    
    void work(int x,int fa,int op) {
    	memset(sam1.tag,0,(sam1.cnt+2)*4);
    	memset(sam2.tag,0,(sam2.cnt+2)*4);
    	if(fa) sam1.mark(x,fa,sam1.tr[1][a[fa]],1),sam2.mark(x,fa,sam2.tr[1][a[fa]],1);
    	else sam1.mark(x,fa,1,0),sam2.mark(x,fa,1,0);
    	sam1.push(),sam2.push();
    	for(int i=1;i<=m;i++) ans+=1ll*op*sam1.tag[sam1.rev[i]]*sam2.tag[sam2.rev[m-i+1]];
    }
    
    void solve(int x) {
    	get_rt(x,0);siz=sz[x];
    	if(siz<=B) {
    		top=0,get_node(x,0);
    		for(int i=1;i<=top;i++) dfs(t[i],0,sam1.qs);
    //		for(int i=1;i<=top;i++) vis[t[i]]=0;
    		return ;
    	}
    	for(int i=head[x];i;i=e[i].nxt) tmp[e[i].to]=sz[e[i].to];
    	work(x,0,1);
    	for(int i=head[x];i;i=e[i].nxt) if(!vis[e[i].to]) work(e[i].to,x,-1);
    	vis[x]=1;
    	for(int i=head[x];i;i=e[i].nxt)
    		if(!vis[e[i].to]) siz=tmp[e[i].to],rt=0,get_rt(e[i].to,x),solve(rt);
    }
    
    int main() {
    	read(n),read(m);B=sqrt(n);
    	for(int i=1,x,y;i<n;i++) read(x),read(y),ins(x,y),ins(y,x);
    	scanf("%s",s+1);
    	for(int i=1;i<=n;i++) a[i]=s[i]-'a';
    	scanf("%s",s+1);
    	sam1.prepare(s);
    	reverse(s+1,s+m+1);
    	sam2.prepare(s);
    	siz=n,f[0]=inf,get_rt(1,0),solve(rt);write(ans);
    	return 0;
    }
    

    [NOI2018] 你的名字

    求区间串\(T\)\(S_{l,r}\)的本质不同公共子串。

    不妨先考虑当\(l = 1,r = |S|\).

    考虑即对\(T\)的每一个前缀求出其最长的后缀满足在\(S\)中出现过。

    考虑直接把\(T\)\(|S|\)的自动机上匹配即可。

    \(l_i\)\(T\)的自动机上的最长长度。

    那么有\(ans = \sum \max(0,l_i - \max(l_{fa_i},lim_{tag_i}))\)

    \(lim_{tag_i}\)为该节点的第一次出现的位置的前缀的最长相同后缀

    考虑如何处理\(l,r\)

    我们发现实际上我们不需要重建SAM。

    按理说可以回滚莫队。

    但是这里给出另外一个做法。

    我们只需要在匹配时查询是否有儿子在\(S_{l,r}\)在这里面即可。

    那么直接线段树合并求\(endpos\)集合即可。

    然后转的时候查询即可。

    这里给出构造\(endpos\)的代码

    点击查看代码
    //晦暗的宇宙,我们找不到光,看不见尽头,但我们永远都不会被黑色打倒。——Quinn葵因
    #include<bits/stdc++.h>
    #define ll long long
    #define N 1000005
    #define M N << 1
    
    int n;
    
    int head[M];
    
    struct P{
    	int li,ri;
    	int sum;
    }T[(M) * 20];
    
    #define ls(x) T[x].li
    #define rs(x) T[x].ri
    #define s(x) T[x].sum
    #define mid ((l + r) >> 1)
    
    int cnt;
    
    inline void change(int &u,int l,int r,int p){
    	if(!u)u = ++cnt;
    	if(l == r){s(u) = 1;/*std::cout<<"CHANGE "<<" "<<u<<" "<<l<<" "<<r<<" "<<p<<" "<<s(u)<<"\n";*/return ;}
    	if(p <= mid)change(ls(u),l,mid,p);
    	if(p > mid)change(rs(u),mid + 1,r,p);
    	s(u) = s(ls(u)) + s(rs(u));
    //	std::cout<<"CHANGE "<<" "<<u<<" "<<l<<" "<<r<<" "<<p<<" "<<s(u)<<"\n";			
    } 
    
    inline void merge(int &u,int las,int l,int r){	
    	if(u && las){
    		T[++cnt] = T[u];
    		u = cnt;
    	}
    	if(!u || !las){u = u + las;return ;}
    	if(l == r){s(u) = 1;/*std::cout<<"MERGE "<<" "<<u<<" "<<las<<" "<<l<<" "<<r<<" "<<s(u)<<"\n";*/return ;}
    	merge(ls(u),ls(las),l,mid);
    	merge(rs(u),rs(las),mid + 1,r);
    	s(u) = s(ls(u)) + s(rs(u));
    //	std::cout<<"MERGE "<<" "<<u<<" "<<las<<" "<<ls(u)<<" "<<rs(u)<<" "<<l<<" "<<r<<" "<<s(u)<<"\n";	
    }
    
    namespace SSAM{
    	struct P{int link,len;int ch[28];int ed;}a[M];
    	int lst = 1;int cnt = 1;int root = 1;
    	inline void insert(int c,int v){
    		int cur = ++cnt;
    		int p = lst;
    		lst = cur;
    		a[cur].ed = v;
    		a[cur].len = a[p].len + 1;
    		while(p && !a[p].ch[c])a[p].ch[c] = cur,p = a[p].link;
    		if(!p)return (void)(a[cur].link = root);
    		int q = a[p].ch[c];
    		if(a[q].len == a[p].len + 1)return (void)(a[cur].link = q);
    		int t = ++cnt;
    		a[t] = a[q];
    		a[t].len = a[p].len + 1;
    		a[q].link = a[cur].link = t;
    		while(p && a[p].ch[c] == q)a[p].ch[c] = t,p = a[p].link;
    	}
    	int t[M],r[M];
    	inline void build(){
    //		for(int i = 1;i <= cnt;++i)
    //		std::cout<<a[i].len<<" "<<a[i].link<<"\n";
    		for(int i = 1;i <= cnt;++i)
    		t[a[i].len] ++ ;
    		for(int i = 1;i <= n;++i)
    		t[i] += t[i - 1];
    		for(int i = 1;i <= cnt;++i)
    		/*std::cout<<"FUCK CHANGE "<<i<<" "<<a[i].ed<<"\n",*/change(head[i],1,n,a[i].ed);
    		for(int i = 1;i <= cnt;++i)
    		r[t[a[i].len] -- ] = i;
    		for(int i = cnt;i >= 2;--i){
    			int u = r[i];
    //			std::cout<<r[i]<<"\n";
    //			std::cout<<"FUCK MERGE "<<a[u].link<<" "<<u<<"\n";
    			merge(head[a[u].link],head[u],1,n);
    		}
    	}
    }
    
    namespace TSAM{
    	struct P{int link,len;int ch[28];int ed;}a[M];
    	int lst = 1;int cnt = 1;int root = 1;
    	inline void insert(int c,int v){
    		int cur = ++cnt;
    		int p = lst;
    		lst = cur;
    		a[cur].ed = v;
    		a[cur].len = a[p].len + 1;
    		while(p && !a[p].ch[c])a[p].ch[c] = cur,p = a[p].link;
    		if(!p)return (void)(a[cur].link = root);
    		int q = a[p].ch[c];
    		if(a[q].len == a[p].len + 1)return (void)(a[cur].link = q);
    		int t = ++cnt;
    		a[t] = a[q];
    		a[t].len = a[p].len + 1;
    		a[q].link = a[cur].link = t;
    		while(p && a[p].ch[c] == q)a[p].ch[c] = t,p = a[p].link;
    	}
    }
    
    char s[N];
    
    ll q;
    
    int main(){
    	scanf("%s",s + 1);
    	n = strlen(s + 1); 
    	for(int i = 1;i <= n;++i)
    	SSAM::insert(s[i] - 'a',i);
    	SSAM::build();
    }
    
    /*
    9
    scbamgepe
    */
    
  • 相关阅读:
    java多线程小节, 总结的不错
    奇瑞风云, 你还在路上么
    android NDK 环境建立
    外企下岗白领正成为“新4050”
    搭积木
    祝MORIENTES在LIVERPOOL有所成就
    简单生活
    为什么要更新
    归去来
    随记一笔
  • 原文地址:https://www.cnblogs.com/dixiao/p/15920792.html
Copyright © 2020-2023  润新知