【BZOJ 4278】[ONTAK2015] Tasowanie

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4278

解题报告

考虑归并排序
唯一麻烦的地方就是两个字符相同时选哪个
我们可以比较后缀的字典序来解决这个问题
具体实现上,我们可以使用SA

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 800009;
const int SGZ = 1001;

int n, m, s[N];
class Suffix_Array{
int *rk, *tmp, sa[N], bot[N], arr1[N], arr2[N], que[N];
public:
	inline void build(int *s, int nn) {
		rk = arr1; tmp = arr2; int sgz = SGZ;
		for (int i = 1; i <= nn; i++) bot[s[i]]++;
		for (int i = 1; i <= sgz; i++) bot[i] += bot[i - 1];
		for (int i = 1; i <= nn; i++) que[bot[s[i]]--] = i;
		rk[que[1]] = sgz = 1; 
		for (int i = 2; i <= nn; i++) rk[que[i]] = (sgz += s[que[i]] != s[que[i - 1]]);
		for (int l = 1; l < nn && sgz < nn; l <<= 1) {
			int tt = 0;
			for (int i = nn - l + 1; i <= nn; i++) tmp[++tt] = i;
			for (int i = 1; i <= nn; i++) if (que[i] > l) tmp[++tt] = que[i] - l;
			for (int i = 0; i <= sgz; i++) bot[i] = 0;
			for (int i = 1; i <= nn; i++) bot[rk[i]]++;
			for (int i = 1; i <= sgz; i++) bot[i] += bot[i - 1];
			for (int i = nn; i; i--) que[bot[rk[tmp[i]]]--] = tmp[i];
			swap(rk, tmp); rk[que[1]] = sgz = 1;
			for (int i = 2; i <= nn; i++) {
				rk[que[i]] = (sgz += tmp[que[i]] != tmp[que[i - 1]] || tmp[que[i] + l] != tmp[que[i - 1] + l]);
			}
		}
	}
	inline int rank(int p) {
		return rk[p];		
	}
}sa;

inline int read() {
	char c = getchar(); int ret = 0, f = 1;
	for (; c < '0' || c > '9'; f = c == '-'? -1: 1, c = getchar());
	for (; '0' <= c && c <= '9'; ret = ret * 10 + c - '0', c = getchar());
	return ret * f;
}

int main() {
	n = read();
	for (int i = 1; i <= n; i++) {
		s[i] = read();
	}
	s[n + 1] = 1001;
	m = read();
	for (int i = 1; i <= m; i++) {
		s[i + n + 1] = read();
	}
	sa.build(s, n + m + 1);
	for (int i = 1, j = 1, k = 1; k <= n + m; k++) {
		if (i <= n && j <= m) {
			if (s[i] < s[j + n + 1] || (s[i] == s[j + n + 1] && sa.rank(i) < sa.rank(j + n + 1))) {
				printf("%d ", s[i++]);
			} else {
				printf("%d ", s[n + 1 + j++]);
			}
		} else if (i <= n) {
			printf("%d ", s[i++]);
		} else {
			printf("%d ", s[n + 1 + j++]);
		}
	}
	return 0;
}

【BZOJ 4650】[NOI2016] 优秀的拆分

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4650
神犇题解:https://blog.sengxian.com/solutions/bzoj-4650

解题报告

主要是统计以每个点为开头/结尾的能划分成两个相等子串的串有多少个
这个是SA的经典应用

当然问能划分成x个相同子串的问题,SA也是一样的做法
比如:https://www.codechef.com/problems/TANDEM

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 60009;
const int SGZ = 26;
const int M = 15;

char pat[N];
int n,c1[N],c2[N];

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

class Suffix_Array{
	char s[N]; int *rak,*que,len,dif;
	int arr1[N],arr2[N],bot[N],sa[N],height[N];
	class Sparse_Table{
		int mn[N][M],len;
		public:
			inline void init(int LEN, int *height) {
				len = LEN; memset(mn,0,sizeof(mn));
				for (int i=1;i<=len;i++) mn[i][0] = height[i];
				for (int j=1,w=1;j<M;j++,w<<=1) {
					for (int i=1,l=len-w;i<=l;i++) {
						mn[i][j] = min(mn[i][j-1], mn[i+w][j-1]);
					}
				} 
			}
			inline int query(int l, int r) {
				if (l > r) swap(l, r); ++l;
				int h = r - l + 1 >> 1, t = 0, L = 1;
				while (L <= h) L<<=1, t++;
				return min(mn[l][t],mn[r-L+1][t]);
			}
	}st;
	public:
		inline void init(char *S, int L) {
			memset(s,0,sizeof(s));
			memset(arr1,0,sizeof(arr1));
			memset(arr2,0,sizeof(arr2));
			for (int i=1;i<=L;i++) s[i] = S[i];
			len = L; build();
		}
		inline int query(int l, int r) {
			if (l < 0 || l > len || r < 0 || r > len) return 0;
			else return st.query(rak[l], rak[r]);
		}
		inline void out() {
			cout<<"----------"<<endl;
			for (int i=1;i<=len;i++) printf("%d ",sa[i]); cout<<endl;
			for (int i=1;i<=len;i++) printf("%d ",height[i]); cout<<endl;
			cout<<"----------"<<endl;
		}
	private:
		inline void build() {
			rak = arr1; que = arr2;
			for (int i=1;i<=SGZ;i++) bot[i] = 0;
			for (int i=1;i<=len;i++) bot[s[i]-'a'+1]++;
			for (int i=2;i<=SGZ;i++) bot[i] += bot[i-1];
			for (int i=1;i<=len;i++) sa[bot[s[i]-'a'+1]--] = i;
			rak[sa[1]] = dif = 1;
			for (int i=2;i<=len;i++) rak[sa[i]] = (dif += (s[sa[i]]!=s[sa[i-1]]));
			for (int l=1,tot;tot=0,l<=len;l<<=1) {
				for (int i=len-l+1;i<=len;i++) que[++tot] = i;
				for (int i=1;i<=len;i++) if (sa[i] > l) que[++tot] = sa[i] - l;
				for (int i=1;i<=dif;i++) bot[i] = 0;
				for (int i=1;i<=len;i++) bot[rak[i]]++;
				for (int i=2;i<=dif;i++) bot[i] += bot[i-1];
				for (int i=len;i;i--) sa[bot[rak[que[i]]]--] = que[i];
				swap(que, rak); rak[sa[1]] = dif = 1;
				for (int i=2;i<=len;i++) 
					if (que[sa[i]]==que[sa[i-1]]&&que[sa[i]+l]==que[sa[i-1]+l]) rak[sa[i]]=dif;
					else rak[sa[i]] = ++dif;
				if (dif >= len) break;
			} 
			for (int i=1;i<=len;i++) {
				int t = max(0, height[rak[i-1]] - 1);
				int p1 = i + t, p2 = sa[rak[i]-1] + t;
				for (;s[p1]==s[p2];p1++,p2++) ++t;
				height[rak[i]] = t;
			}
			st.init(len, height);
		}
}dir,rev; 

int main() {
	for (LL T=read(),vout;vout=0,T;T--) {
		memset(c1,0,sizeof(c1)); memset(c2,0,sizeof(c2));
		scanf("%s",pat+1); n = strlen(pat+1);
		dir.init(pat, n); 
		for (int i=1,j=n;i<j;i++,j--) swap(pat[i], pat[j]);
		rev.init(pat, n);
		for (int l=1,suf,pre,L,R;l<=n;l++) {
			for (int i=1;i<=n;i+=l) {
				suf = dir.query(i, i+l); 
				pre = rev.query(n-i+2, n-i-l+2) + 1;
				L = max(i, i + l - pre);
				R = min(i + l - 1, i + suf - 1);
				if (L <= R) {
					c1[L+l]++; c1[R+l+1]--;
					c2[L-l]++; c2[R-l+1]--;
				}
			}
		}
		for (int i=1,t1=c1[0],t2=c2[0];i<=n;i++) {
			t1 += c1[i]; t2 += c2[i];
			vout += (LL)t1 * t2;
		}
		printf("%lld\n",vout);
	}
	return 0;
}

【BZOJ 4319】[CERC2008] Suffix Reconstruction

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4319
神犇题解:http://www.cnblogs.com/Skyminer/p/6414088.html

解题报告

之前在做这个题的时候就听说过这题
但当时想了一会儿,感觉不可做,以为是他们记错题目了 QwQ
今天居然看到原题了,但想了很久,还是只会$O(26 \cdot n \log{n})$

先来说说$O(26 \cdot n \log{n})$的做法吧!
我们可以从上到下单个字符填,就是尽量往小的填,填完以后$O(n)$验证一遍

我们考虑分出不同的地方,因为填了都是比他小的,所以如果冲突,那一定是之前的太大了
但之前的部分已经贪心尽量小地填了,所以之前的肯定不能改小了,只能把当前位改大

所以这么做是对的,时间复杂度:$O(26 \cdot n^2)$
不难发现$SA$数组上一定是一段连续的A,之后一段连续的B。后面也是这样
于是我们可以二分每一个字符地分界点,这样复杂度就是$O(26 \cdot n \log{n})$的了

正解的话,是$O(n)$的,仍然是从小到大贪心
我们考虑计算$height$数组时候那个Trick的正确性证明
如果$rank[sa[i]+1] > rank[sa[i+1]+1]$的话,那么$sa[i]$填的字符一定比$sa[i+1]$小
否则他俩相等也没有关系,因为后面还是会把他们区分出来
于是贪心就可以$O(1)$验证了,于是总的时间复杂度就是线性的了!

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 500009;

int n,sa[N],rak[N];
char ans[N];

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

int main() {
	n = read(); 
	for (int i=1;i<=n;i++) rak[sa[i]=read()]=i;
	ans[sa[1]] = 'a';
	for (int i=1,w='a';i<n;i++) (rak[sa[i]+1]>rak[sa[i+1]+1])? ans[sa[i+1]]=++w: ans[sa[i+1]]=w;
	if (ans[n] <= 'z') printf("%s",ans+1);
	else puts("-1");
	return 0;
}

【日常小测】Different Trips

题目大意

给定一棵$n(n \le 10^5)$个结点的树,每一个结点的特征值定义为这个结点的度
询问从根节点走到叶子结点的所有路径中本质不同的字符串的数量

解题报告

这题就是诸神眷顾的幻想乡的弱化版,撸一发广义SAM就可以了

不过GEOTCBRL考完之后给我们增加了一点姿势水平:

广义后缀自动机如果使用DFS建立的话,时间复杂度是$O($叶子结点深度和$)$的
如果使用BFS来建立的话,时间复杂度是$O(n \cdot$字符集大小$)$

这在15年刘研绎的集训队论文《后缀自动机在字典树上的拓展》中有详细的证明
其中还给出了构造极端数据的构造方法
所以这题字符集大小这么大的话,这样会T的
可能只有后缀数组才能做 QwQ

Code

这辣鸡代码是可以卡的,泥萌不要卡啊…….

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
const int N = 2000009;
 
int n,head[N],nxt[N],to[N],in[N];
 
inline int read() {
    char c=getchar(); int ret=0,f=1;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret*f;
}
 
inline void AddEdge(int u, int v) {
    static int E = 1; in[u]++; in[v]++;
    to[++E] = v; nxt[E] = head[u]; head[u] = E;
    to[++E] = u; nxt[E] = head[v]; head[v] = E;
}
 
class Suffix_Automaton{
    int tot,len[N],fail[N],vis;
    map<int,int> ch[N];
    public:
        inline Suffix_Automaton() {tot=1;}
        inline int insert(int t, int c) {
            if (ch[t]&&len[ch[t]]==len[t]+1) return ch[t];
            int w = ++tot; len[w] = len[t] + 1;
            for (;t&&!ch[t];t=fail[t]) ch[t] = w;
            if (!t) fail[w] = 1;
            else if (len[ch[t]] == len[t] + 1) fail[w] = ch[t];
            else {
                int nt = ++tot, pt = ch[t];
                ch[nt] = ch[pt]; len[nt] = len[t] + 1;
                fail[nt] = fail[pt]; fail[pt] = fail[w] = nt;
                for (;t&&ch[t]==pt;t=fail[t]) ch[t] = nt;
            } return w;
        }
        inline LL GetAns() {
            LL ret = 0;
            for (int i=2;i<=tot;i++)
                ret += len[i] - len[fail[i]];
            return ret;
        }
}SAM;
 
void DFS(int w, int f, int ins) {
    int nins = SAM.insert(ins, in[w]);
    for (int i=head[w];i;i=nxt[i]) {
        if (to[i] != f) {
            DFS(to[i], w, nins);
        }
    }
}
 
int main() {
    n = read();
    for (int i=1;i<n;i++) AddEdge(read(), read());
    DFS(1, 1, 1);
    printf("%lld\n",SAM.GetAns());
    return 0;
}

【BZOJ 4199】[NOI2015] 品酒大会

相关链接

题目传送门Ⅰ:http://uoj.ac/problem/131
题目传送门Ⅱ:http://www.lydsy.com/JudgeOnline/problem.php?id=4199
神犇题解:https://blog.sengxian.com/solutions/bzoj-4199

解题报告

其实sengxian都写了题解了,我还有什么写的意义 _(:з」∠)_
不过还是简单说两句吧!

我们考虑把SA建出来,height数组搞出来
然后把height数组排个序
从小到大依次合并上去就可以辣!

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 600009; 
const LL INF = 1e18;

int n,val[N],sz[N],mx[N],mn[N]; 
int height[N],bot[N],sa[N],arr[N];
int *rak,*que,arr1[N],arr2[N],fa[N];
char pat[N]; LL ans1,ans2;
stack<pair<LL,LL> > ans;

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

inline bool cmp(const int &a, const int &b) {
	return height[a] > height[b];
}

class Suffix_Array{
	public:
		inline void build() {
			rak = arr1; que = arr2;
			for (int i=1;i<=n;i++) bot[pat[i]-'a'+1]++;
			for (int i=1;i<=26;i++) bot[i] += bot[i-1];
			for (int i=1;i<=n;i++) sa[bot[pat[i]-'a'+1]--] = i;
			for (int ty=0,i=1;i<=n;i++) rak[sa[i]] = pat[sa[i]]==pat[sa[i-1]]? ty: ++ty;
			for (int l=1,tot=0,ty;l<n;l<<=1,tot=0) {
				memset(bot,0,sizeof(bot));
				for (int i=n-l+1;i<=n;i++) que[++tot] = i;
				for (int i=1;i<=n;i++) if (sa[i]>l) que[++tot] = sa[i] - l;
				for (int i=1;i<=n;i++) bot[rak[i]]++;
				for (int i=1;i<=n;i++) bot[i] += bot[i-1];
				for (int i=n;i;i--) sa[bot[rak[que[i]]]--] = que[i];
				swap(rak, que); rak[sa[1]] = ty = 1; bot[1] = 1;
				for (int i=2;i<=n;i++) 
					if (que[sa[i]] == que[sa[i-1]] && que[sa[i]+l] == que[sa[i-1]+l]) rak[sa[i]] = ty;
					else bot[rak[sa[i]] = ++ty] = 0;
				if (ty >= n) break;
			}
			Get_Height();
		}
		inline void solve() { 
			for (int i=1;i<=n;i++) {
				arr[i] = fa[i] = i; sz[i] = 1; 
				mn[i] = mx[i] = val[sa[i]];
			}
			sort(arr+1, arr+1+n, cmp);
			for (int i=n-1,j=1;~i;i--) {
				for (;height[arr[j]]>=i;++j) update(arr[j]);
				ans.push(make_pair(ans1, ans2));
			} 
			for (;!ans.empty();ans.pop()) 
				printf("%lld %lld\n",ans.top().first, ans.top().second);
		}	
	private:
		inline void Get_Height() {
			for (int i=1,len,p1,p2;i<=n;i++) {
				len = max(height[rak[i-1]] - 1, 0);
				p1 = i + len; p2 = sa[rak[i]-1] + len;
				while (pat[p1] == pat[p2]) ++p1, ++p2, ++len;
				height[rak[i]] = len;
			} height[1] = -1;
		}
		int find(int x) {return fa[x]==x?x:find(fa[x]);}
		inline void update(int i) { 
			static int E = 1; if (E) E = 0, ans2 = -INF;
			int f1 = find(i), f2 = find(i-1);
			ans1 += (LL)sz[f1] * sz[f2];
			ans2 = max(ans2, (LL)mx[f2] * mx[f1]);
			ans2 = max(ans2, (LL)mn[f2] * mn[f1]);
			sz[f1] += sz[f2]; fa[f2] = f1;
			mx[f1] = max(mx[f1], mx[f2]);
			mn[f1] = min(mn[f1], mn[f2]);
		}
}SA;

int main() {
	n = read();
	scanf("%s",pat+1);
	for (int i=1;i<=n;i++) val[i] = read();
	SA.build();
	SA.solve();
	return 0;
}

—————————— UPD 2017.7.10 ——————————
显然这货用SAM也是可做的

【日常小测】最长公共前缀

题目大意

给定一棵以$1$号点为根、大小为$n$的有根树($n \le 10^5$),每一条边上有一个小写英文字母
给定$m$个询问,第$i$个询问包含一个长度为$t_i$的点集(元素可重,$\sum{t_i} \le 2 \cdot 10^5$),询问$\sum\limits_{a=1}^{t_i}{\sum\limits_{b=a+1}^{t_i}{lcp(a,b)}}$

定义$s_a$为从a出发走向根,将边上的字符按顺序写下来构成的字符串
定义$lcp(a,b)$为$s_a$与$s_b$的最长公共前缀

下面举一个栗子
若树长成这样:

那么$s_5=cb,s_4=cbb$
更进一步,若某次询问的点集为$\{4,5\}$那么答案为$2$

解题报告

看到这种树上的字符串,我能想到AC自动机,或者广义后缀自动机
想了想AC自动机做不了,那就广义后缀自动机来做咯!

考虑后缀自动机上的Fail树
一个点的祖先实际上是这个点代表的字符串的后缀
那么题目中的$lcp(a,b)$就变成了$lca(a,b)$
于是对于每一次询问,我们建一个虚树出来
然后在虚树上DFS一次,统计一下答案就好啦!

另外,这题用后缀数组也可以做!
听武爷讲一讲,大概就是先把Trie树建出来
同时记录下每一次倍增的$Rank$数组(波兰表……)
然后就正常倍增,写法已经和罗穗骞的后缀数组的实现很不同了

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
const int N = 200009;
const int M = N << 1;
 
int n,m,E,head[N],nxt[M],to[M],col[M],pos[N],id[N];
 
inline int read() {
    char c=getchar(); int ret=0,f=1;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret * f;
}
 
inline void Add_Edge(int u, int v, int c = 0) {
    to[++E] = v; nxt[E] = head[u]; head[u] = E; col[E] = c;
    to[++E] = u; nxt[E] = head[v]; head[v] = E; col[E] = c;
}   
 
class Suffix_Automaton{
	int cnt=1,ch[N][26],fail[N],vis[N],dep[N],sum[N];
	int tot,arr[N],que[N],len[N],fa[N][20],_hash[N];
	vector<int> G[N]; LL ans_tmp;
    public:
        inline int insert(int t, int cc) {
            if (ch[t][cc] && len[ch[t][cc]] == len[t] + 1) return ch[t][cc];
            int tail = ++cnt; len[tail] = len[t] + 1;
            while (!ch[t][cc] && t) ch[t][cc] = tail, t=fail[t];
            if (!t) fail[tail] = 1;
            else {
                if (len[ch[t][cc]] == len[t] + 1) fail[tail] = ch[t][cc];
                else {
                    int nw = ++cnt, w = ch[t][cc]; len[nw] = len[t]+1;
                    memcpy(ch[nw], ch[w], sizeof(ch[nw]));
                    fail[nw] = fail[w]; fail[w] = fail[tail] = nw;
                    for (;ch[t][cc]==w;t=fail[t]) ch[t][cc] = nw;
                }
            } return tail;
        }
        inline void Build_Fail_Tree() {
            memset(head,0,sizeof(head)); E = 0;
            for (int i=2;i<=cnt;i++) Add_Edge(fail[i], i);
            dfs(1, 1);
            for (int j=1;j<20;j++) {
                for (int i=1;i<=cnt;i++) {
                    fa[i][j] = fa[fa[i][j-1]][j-1];
                }
            }   
        }
        inline LL solve(int nn) {
            for (int i=1;i<=nn;i++) arr[i] = pos[read()];
            sort(arr+1, arr+1+nn, [](const int &a, const int &b) {return id[a] < id[b];});
            for (int i=1;i<=nn;i++) _hash[i] = arr[i];
            int mm = nn; nn = unique(arr+1, arr+1+nn) - arr - 1;
            for (int i=1;i<=nn;i++) sum[i] = 0;
            for (int i=1,j=1;i<=mm;i++) {
                while (arr[j] != _hash[i]) j++;
                sum[j]++;
            }
            que[tot=1] = 1; int MX = 1;
            for (int i=1,lca;i<=nn;i++) {
                vis[arr[i]] = sum[i];
                lca = LCA(que[tot], arr[i]);
                while (dep[que[tot]] > dep[lca]) {
                    if (dep[que[tot-1]] >= dep[lca]) G[que[tot-1]].push_back(que[tot]);
                    else G[lca].push_back(que[tot]);
                    --tot;
                }
                if (que[tot] != lca) que[++tot] = lca;
                if (arr[i] != que[tot]) que[++tot] = arr[i];
                MX = max(MX, tot);
            }
            while (tot > 1) G[que[tot-1]].push_back(que[tot]), --tot;
            ans_tmp = 0;
            Get_Ans(1);
            return ans_tmp;
        }
    private:
        void dfs(int w, int f) {
            static int id_count = 0;
            id[w] = ++id_count;
            fa[w][0] = f; dep[w] = dep[f] + 1;
            for (int i=head[w];i;i=nxt[i]) {
                if (to[i] != f) {
                    dfs(to[i], w);
                }
            }
        } 
        int Get_Ans(int w) {
            int ret = vis[w]; 
            if (w > 1) ans_tmp += vis[w] * (vis[w] - 1ll) / 2 * len[w]; 
            for (int i=G[w].size()-1,tmp;~i;i--) { 
                tmp = Get_Ans(G[w][i]);
                ans_tmp += (LL)tmp * ret * len[w];
                ret += tmp;
            }
            vis[w] = 0; 
            G[w].clear();
            return ret;
        }
        inline int LCA(int a, int b) {
            if (dep[a] < dep[b]) swap(a, b);
            for (int i=19;~i;i--) if (dep[fa[a][i]] >= dep[b]) a = fa[a][i];
            if (a == b) return a;
            for (int i=19;~i;i--) if (fa[a][i] != fa[b][i]) a = fa[a][i], b = fa[b][i];
            return fa[a][0];
        }   
}SAM;
 
void DFS(int w, int f, int t) {
    pos[w] = t;
    for (int i=head[w];i;i=nxt[i]) {
        if (to[i] != f) {
            int nt = SAM.insert(t, col[i]);
            DFS(to[i], w, nt);
        }
    }
}
 
int main() {
    n = read(); char pat[10];
    for (int i=1,u,v;i<n;i++) {
        u = read(); v = read();
        scanf("%s",pat+1);
        Add_Edge(u, v, pat[1] - 'a');
    }   
    DFS(1, 1, 1);
    SAM.Build_Fail_Tree();
    for (int i=read();i;i--) 
        printf("%lld\n",SAM.solve(read()));
    return 0;
}

【BZOJ 4556】[TJOI2016&HEOI2016] 字符串

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4556
神犇题解:http://www.cnblogs.com/Var123/p/5575518.html

解题报告

这个题目实际上是询问 $s(c,d)$ 的 $LCP$
于是我们考虑二分这个长度 $L$
那么这个 $L$ 就相当于在 $SA$ 的数组上划了一个区间
不难发现如果这个区间中存在一个后缀的起点属于 $s(a,b)$ ,这个长度就是合法的
于是我们二分一下,然后搞一个后缀数组和函数式线段树就可以啦!

【BZOJ 4516】[SDOI2016] 生成魔咒

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4516

考试的时候,一看到要动态维护就把SA扔一边凉快去了QAQ
想一想,不能这么草率啊!
来说一说SA和SAM两种做法

1) SAM:
很明显就是求一个字符串本质不同的字串个数
SAM本身就是增量构造,于是就成裸题了

2) SA:
考虑离线之后,先逆序建一个完整的出来
那么加入一个字符,实际上就是加入了一个后缀
那么其对于答案的贡献,就是原来的height数组搞一搞
实现的话,搞一个RMQ什么的就好
代码的话,我们来召唤Menci:https://oi.men.ci/sdoi2016-incantation/

自己太懒,于是就只写了SAM的代码:

#include<bits/stdc++.h>
#include<tr1/unordered_map>
#define LL long long
using namespace std;
using namespace tr1;

const int N = 200000+9; 

inline int read(){
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

namespace Suffix_Automaton{
	#define SAM Suffix_Automaton
	int len[N],fail[N],cnt,tail;
	unordered_map<int,int> ch[N]; 
	
	inline int Extend(int c) {
		int w = tail; tail = ++cnt; len[tail] = len[w] + 1;
		while (w&&!ch[w]) ch[w] = cnt, w = fail[w];
		if (!w) fail[tail] = 1;
		else {
			if (len[ch[w]] == len[w]+1) fail[tail] = ch[w];
			else {
				int to = ch[w]; ch[++cnt] = ch[to];
				fail[cnt] = fail[to]; fail[to] = cnt; fail[tail] = cnt; 
				len[cnt] = len[w] + 1; while (ch[w] == to) ch[w] = cnt, w = fail[w];
			}
		} return len[tail] - len[fail[tail]];
	}
	
	inline void solve(int n) {
		cnt = tail = 1; 
		for (LL i=1,vout=0;i<=n;i++) 
			printf("%lld\n",vout+=Extend(read()));
	}
};

int main(){
	SAM::solve(read());
	return 0;
}

—– UPD 2016.9.29 —–
这™map比unordered_map快这么多有几个意思QAQ
475864785645
这种感觉……
真·生无可恋
V5[YANBJ`4%A2`%PIH$BH_F

【Codeforces 706C】Hard problem

题目传送门:http://codeforces.com/contest/706/problem/C
数据生成器:http://paste.ubuntu.com/23048130/

不要问我这道题我做了多久QAQ

一开始是想道2-SAT
后来是写SA一直不停TLE
然后是写O(n)的暴力,仍然T到死

最后发现,是strlen的时间复杂度不对QAQ
之前,我一直以为这货是O(1)的
就是先用黑科技搞定尾地址,然后首尾地址减一减
结果查一查,发现这货是O(n)的QAQ

#include<iostream>
#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;

const int MAXN = 200000+9;

LL f[2][MAXN];
int pos[MAXN],n,c[MAXN],rev[MAXN],end[MAXN];
char pat[MAXN];

inline bool judge(int p1, int p2, int len, bool sa){
	int w = 0; char *s1 = &pat[p1], *s2 = &pat[p2]; 
	while (w < len && *s1 == *s2) w++, s1++, s2++;
	if ((w == len && sa) || *s1 < *s2) return true; 
	else return false;
}

inline int read(){
	char c=getchar(); int ret=0;
	while (c<'0'||c>'9') c=getchar();
	while (c<='9'&&c>='0'){ret=ret*10+c-'0';c=getchar();}
	return ret;
}

int main(){
	int len = 0; cin>>n; 
	for (int i=1;i<=n;i++) c[i] = read();
	for (int i=1;i<=n;i++) {
		pos[i] = len+1; gets(pat+len+1);
		len = strlen(pat+pos[i])+pos[i]-1; 
		end[i] = len;
	} 
	for (int i=1;i<=len;i++) pat[len*2-i+1] = pat[i];	
	for (int i=1;i<=n;i++) rev[i] = len*2-end[i]+1; 
	for (int i=1;i<=n;i++) end[i] = end[i] - pos[i] + 1; 
	
	memset(f,0x3f,sizeof(f)); f[0][1] = 0; f[1][1] = c[1];
	for (int i=2,lim=min(end[i],end[i-1]);i<=n;i++,lim=min(end[i],end[i-1])) {
		if (judge(pos[i-1], pos[i], lim, end[i]>=end[i-1])) f[0][i] = min(f[0][i], f[0][i-1]);
		if (judge(pos[i-1], rev[i], lim, end[i]>=end[i-1])) f[1][i] = min(f[1][i], f[0][i-1] + c[i]);
		
		if (judge(rev[i-1], pos[i], lim, end[i]>=end[i-1])) f[0][i] = min(f[0][i], f[1][i-1]);
		if (judge(rev[i-1], rev[i], lim, end[i]>=end[i-1])) f[1][i] = min(f[1][i], f[1][i-1] + c[i]);
	}
	LL vout = min(f[0][n], f[1][n]);
	cout<<(vout<f[0][MAXN-1]?vout:-1)<<endl;
	return 0;
}

【NOI五连测】[D3T2] 加密

题目传送门:http://pan.baidu.com/s/1qYPBdpU
数据生成器:http://paste.ubuntu.com/19351549/
OJ传送门:http://oi.cdshishi.net:8080/Problem/1719

这道题目,一看,SA的板题嘛!于是很高兴的码了SA
码完之后一对拍,发现,好像常数很大的样子,然而一看数据范围,嗯,一定是O(nlogn)的复杂度
结果,又被卡常了QAQ

说一说SA的做法:
首先在SA上二分,搞出len
然后再次在SA上二分,搞出最小的标号
然后总体复杂度O(6*n*log(n)),于是T了一个点QAQ

再来说一说SAM的做法(QJC说:这不是SAM的一眼题吗QAQ)
两个字串的lcp,是fail树上的lca,于是考虑在lca上染色的话,O(n)即可
另外,此题不是要求的不是后缀关系,是前缀关系,于是我们倒着建即可

SA版本:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int MAXN = 1000000+9;
const int SGZ = 26;
const int INF = 100000000;

char pat[MAXN];
int n;

namespace Suffix_Array{
	#define SA Suffix_Array
	int height[MAXN],sa[MAXN],t1[MAXN],t2[MAXN],bot[MAXN];
	int *tmp,*rank,m,K[MAXN],LEN[MAXN];
	
	inline void ST_Advance(){
		for (int i=1,k,len;i<=n;i++) {
			k = 0, len = 1;
			while (len < i) len *= 2, k++;
			K[i] = k; LEN[i] = len;
		}
	}
	
	struct Sparse_Table{
		int MN[MAXN][20];
		
		inline void init(int *arr){
			for (int i=1;i<=n;i++) MN[i][0] = arr[i];
			for (int k=1,lim=2;k<=19;k++,lim=1<<k) for (int i=1;i<=n-lim+1;i++)
				MN[i][k] = min(MN[i][k-1], MN[i+(1<<(k-1))][k-1]);
		}
		
		inline int query(int a, int b){
			if (a > b) swap(a, b); 
			int sta=(b-a+2)/2,k=K[sta],len=LEN[sta];
			return min(MN[a][k], MN[b-len+1][k]);
		}
	}ST_num,ST_hei;
	
	inline void GetHeight(){
		for (int i=1;i<=n;i++) if (rank[i] > 1) {
			int sta = max(0, height[rank[i-1]]-1);
			int p1 = sta + i, p2 = sta + sa[rank[i]-1];
			while (pat[p1++] == pat[p2++]) sta++;
			height[rank[i]] = sta;
		}
	}
	
	inline void build(){
		tmp = t1, rank = t2;
		n = strlen(pat+1); m = SGZ;
		for (int i=1;i<=n;i++) bot[tmp[i]=pat[i]-'a'+1]++;
		for (int i=2;i<=m;i++) bot[i] += bot[i-1];
		for (int i=1;i<=n;i++) sa[bot[tmp[i]]--] = i;
		rank[sa[1]] = m = 1;
		for (int i=2;i<=n;i++) 
			if (tmp[sa[i]] != tmp[sa[i-1]]) rank[sa[i]] = ++m;
			else rank[sa[i]] = m;
		
		for (int k=1;k<=n;k*=2) { int T = 0;
			for (int i=n-k+1;i<=n;i++) tmp[++T] = i;
			for (int i=1;i<=n;i++) if (sa[i] > k) tmp[++T] = sa[i]-k;
			for (int i=1;i<=m;i++) bot[i] = 0;
			for (int i=1;i<=n;i++) bot[rank[i]]++;
			for (int i=2;i<=m;i++) bot[i] += bot[i-1];
			for (int i=n;i;i--) sa[bot[rank[tmp[i]]]--]  = tmp[i];
			
			swap(tmp, rank); rank[sa[1]] = m = 1;
			for (int i=2;i<=n;i++) 
				if (tmp[sa[i]] != tmp[sa[i-1]] || tmp[sa[i]+k] != tmp[sa[i-1]+k]) rank[sa[i]] = ++m;
				else rank[sa[i]] = m; 
			
			if (m >= n) break;
		}
		GetHeight();
		ST_Advance();
		ST_num.init(sa);
		ST_hei.init(height);
	}
	
	inline void solve(){
		for (int w=1,tag=0;w<n;tag=0) {
			int mid, l = 1, r = rank[w] - 1, len = 0, ans = 0;
			while (l <= r) {
				mid = (l + r) / 2;
				if (ST_num.query(rank[w]-mid,rank[w]-1) < w) r = mid-1, len = mid;
				else l = mid+1;
			}
			if (len) ans = ST_hei.query(rank[w]-len+1, rank[w]);
			
			l = 1; r = n - rank[w]; len = 0;
			while (l <= r) {
				int mid = (l + r) / 2;
				if (ST_num.query(rank[w]+1, rank[w]+mid) < w) r = mid-1, len = mid;
				else l = mid+1; 
			} 
			if (len) ans = max(ans, ST_hei.query(rank[w]+1, rank[w]+len));
			
			if (ans) {
				printf("%d ",ans); tag = ans;
				l = 1; r = rank[w]; len = 0; ans = INF;
				while (l <= r) {
					mid = (l + r) / 2;
					if (ST_hei.query(rank[w], rank[w]-mid+1) >= tag) l = mid+1, len = mid;
					else r = mid-1; 
				} 
				if (len) ans = ST_num.query(rank[w]-len,rank[w]-1);
				l = 1; r = n-rank[w]; len = 0;
				while (l <= r) {
					mid = (l + r) / 2;
					if (ST_hei.query(rank[w]+1, rank[w] + mid) >= tag) l = mid+1, len = mid;
					else r = mid-1;			
				}
				if (len) ans = min(ans, ST_num.query(rank[w]+1, rank[w]+len));
				printf("%d ",ans-1); w += tag;
			} else {
				printf("%d %d ",-1,(int)pat[w]);
				w ++;
			}
		}
	}
};

int main(){
	freopen("encrypt.in","r",stdin);
	freopen("encrypt.out","w",stdout);
	scanf("%s",pat+1);
	SA::build();
	SA::solve();
	return 0;
}

SAM版本:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int MAXN = 1000000+9;
const int SGZ = 27;

char pat[MAXN];
int n;

namespace Suffix_Automaton{
	#define SAM Suffix_Automaton
	int ch[MAXN][SGZ],fail[MAXN],tag[MAXN],W=1;
	int cnt,last,dep[MAXN],pos[MAXN];
	
	inline void extend(int i, int c){
		int w = last; dep[pos[i]=last=++cnt] = dep[w]+1; 
		while (w && !ch[w]) ch[w] = last, w = fail[w];
		if (!ch[w]) fail[last] = 1;
		else {
			int nw = ch[w];
			if (dep[nw] == dep[w] + 1) fail[last] = nw;
			else {
				int nnw = ++cnt; dep[nnw] = dep[w] + 1;
				memcpy(ch[nnw],ch[nw],sizeof(ch[nw]));
				fail[nnw] = fail[nw]; fail[nw] = fail[last] = nnw;
				while (ch[w] == nw) ch[w] = nnw, w = fail[w];
			}
		}
	}
	
	inline void sign(int w, int val) {
		while (w && !tag[w]) 
			tag[w] = val, w = fail[w];
		if (W == val) {
			if (w <= 1) printf("-1 %d ",(int)pat[val]), W++;
			else printf("%d %d ",dep[w],tag[w]-1), W += dep[w]; 
		}
	}
	
	inline void build(char *s){
		n = strlen(s+1); cnt = last = 1;
		for (int i=n;i;i--) extend(i, pat[i]-'a'+1);
		for (int i=1;i<=n;i++) sign(pos[i], i);
	}
};

int main(){
	scanf("%s",pat+1);
	SAM::build(pat);
	return 0;
} 

看看SA将近4k的代码
再看看SAM只有1.2k的代码
SA,你还要我怎么爱你QAQ

【NOI六连测】[D2T3] 圈圈

题目传送门:http://pan.baidu.com/s/1pLvQmx1
离线版题目:http://paste.ubuntu.com/18302981/
数据传送门:http://pan.baidu.com/s/1dF0ovZj

这一道题,std是后缀树,然而被YYY踩了,居然一个Hash / SA就可捉QAQ
还是说正事吧。
对于每一次++,分两种情况讨论:
1)有至少一个数变成0:
对于这一种情况呢,明显,字典序最小的循环串的开头,只会在这些变成0的位置中产生
所以问题就变为:比较一些给定的字符串的大小。这个看起来也不是很能做的样子。
但如果我们只考虑两两比较,那么去掉这两个串的公共前缀,我们只需要比较一个字符就可以确定大小
所以拿SA / Hash处理出公共前缀后,比较单个字符的大小即可
2)没有数变成零:
对于这一种情况,答案不会变。水涨船高嘛!
于是这个题目就搞定啦!哎,这么简单!我这个纸张考试的时候怎么没有想到呢?QAQ
%%%YYY, Hash踩标程

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int MAXN = 200000+9;
const int SGZ = 51000;

int n,m,k,N,M,arr[MAXN],ord[MAXN],now,tot,ans;

inline int read(){
	char c=getchar(); int buf=0,f=1;
	while (c<'0'||c>'9'){if (c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0'){buf=buf*10+c-'0';c=getchar();}
	return buf*f;
}

inline bool cmp_sort(const int a, const int b){
	return arr[a] > arr[b];
}

namespace Suffix_Array{
	#define SA Suffix_Array
	int sa[MAXN],height[MAXN],t1[MAXN],t2[MAXN],bot[MAXN];
	int *tmp,*rank,m;
	
	namespace Sparse_Table{
		#define ST Sparse_Table
		int MN[MAXN][20];
		
		inline void init(){
			for (int i=1;i<=n;i++) MN[i][0]=height[i];
			for (int k=1;k<=16;k++)	for (int i=1;i<=n;i++)
				MN[i][k] = min(MN[i][k-1],MN[i+(1<<k-1)][k-1]); } inline int query(int l, int r){ if (l > r) swap(l,r); l++;
			int k=0,len=1,L=(r-l+1);
			while (len <= L/2) len*=2,k++;
			return min(MN[l][k],MN[r-len+1][k]);
		}
	};
	
	inline void GetHeight(){
		for (int i=1;i<=n;i++) if (rank[i]>1){
			int sta = max(0,height[rank[i-1]]-1);
			int p1=i+sta, p2=sa[rank[i]-1]+sta;
			while (arr[p1++]==arr[p2++]) sta++;
			height[rank[i]] = sta;
		}
	}
	
	inline void build(){
		tmp=t1; rank=t2; m=SGZ;
		for (int i=1;i<=m;i++) bot[i] = 0;
		for (int i=1;i<=n;i++) bot[tmp[i]=arr[i]+1]++;
		for (int i=2;i<=m;i++) bot[i] += bot[i-1];
		for (int i=n;i;i--) sa[bot[tmp[i]]--] = i;
		rank[sa[1]] = m = 1;
		for (int i=2;i<=n;i++)
			rank[sa[i]] = (tmp[sa[i]]==tmp[sa[i-1]])?m:++m;
			
		for (int k=1;k<=n;k*=2){ int T=0;
			for (int i=n-k+1;i<=n;i++) tmp[++T] = i;
			for (int i=1;i<=n;i++) if (sa[i] > k) tmp[++T] = sa[i]-k;
			for (int i=1;i<=m;i++) bot[i] = 0;
			for (int i=1;i<=n;i++) bot[rank[i]]++;
			for (int i=2;i<=m;i++) bot[i] += bot[i-1];
			for (int i=n;i;i--) sa[bot[rank[tmp[i]]]--] = tmp[i];
			
			swap(tmp,rank);
			rank[sa[1]] = m = 1;
			for (int i=2;i<=n;i++) if (tmp[sa[i]]==tmp[sa[i-1]]&&tmp[sa[i]+k]==tmp[sa[i-1]+k]) rank[sa[i]]=m; else rank[sa[i]] = ++m; if (m >= n) break;
		}
		GetHeight();
		ST::init();
	}
	
	inline int query(int a, int b){
		return ST::query(rank[a],rank[b]);
	}
};

int main(){
	freopen("cyclic.in","r",stdin);
	freopen("cyclic.out","w",stdout);
	scanf("%d%d%d",&N,&M,&k);
	for (int i=1;i<=N;i++) ord[i]=i,
		arr[i]=arr[i+N]=read();
	n = N*2; now = M-1; tot=1; SA::build();
	sort(ord+1,ord+1+N,cmp_sort);
	
	for (int i=1;i<=n;i++) if (SA::sa[i]<=N) {ans=SA::sa[i];break;}
	printf("%d\n",arr[ans+k-1]);
	for (int i=1;i<M;i++,now--){
		if (tot <= N && arr[ord[tot]]==now){
			ans = ord[tot++];
			while (tot <= N && arr[ord[tot]]==now){ int same = SA::query(ans, ord[tot]); int t = (arr[ans+same]+i)%M-(arr[ord[tot]+same]+i)%M; if (t >= 0) ans = ord[tot]; tot++;
			} printf("%d\n",(arr[ans+k-1]+i)%M);
		} else printf("%d\n",(arr[ans+k-1]+i)%M);	
	}
	
	return 0;
} 

【POJ 3693】Maximum repetition substring

相关链接

题目传送门:http://poj.org/problem?id=3693
数据生成器:http://paste.ubuntu.com/18156993/

题目大意

给定长度为$n(n \le 10^5)$,问循环次数最多的子串循环多少次
请输出字典序最小的解

解题报告

这是SA的 论文题 + 神题
而且SAM貌似不可捉的样子 (╯-_-)╯╧╧
求符合要求的串已经是身心具疲,这题还要字典序最小,TM恶心到不行 ( ▼-▼ )

还是说一说怎么求符合要求的串吧:
枚举符合要求的串的长度L,那么符合要求的串一定在s[1],s[1+L],s[1+2*L]…..等位置出现(跟今年APIO的交互题有点像)
于是我们就直接height[] + ST表搞定整节循环的部分,这个地方和KMP差不多
只与前面有多少相同的,看起来不好求,但实际上如果对于答案有贡献,那么一定是补足循环节

然后是如何让字典序最小:
一般的题目没这题这么恶心,这题尤其恶心! MD再掀一次桌子都无法压抑内心的悲伤 (╯-_-)╯╧╧
这个好像只能用SA数组从小到大枚举,然后判断
哎呀,考场上要是遇到这个题,一定做不对QAQ

Code

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int MAXN = 200000+9;
const int SIGMA_SIZE = 26;

char pat[MAXN];

namespace Suffix_Array{
	#define SA Suffix_Array
	int sa[MAXN],height[MAXN],t1[MAXN],t2[MAXN];
	int bot[MAXN],*tmp,*rank,m,n;
	int Tm,len[MAXN],TT,cnt;
	
	namespace Sparse_Table{
		#define ST Sparse_Table
		int MN[MAXN][20];
		
		inline void build(){
			for (int i=1;i<=n;i++) memset(MN[i],0,sizeof(MN[i]));
			for (int i=1;i<=n;i++) MN[i][0] = height[i];
			for (int i=1;i<18;i++){
				for (int j=1;j<=n;j++)
					MN[j][i] = min(MN[j][i-1],MN[j+(1<<(i-1))][i-1]);
			}
		}
		
		inline int query(int l, int r){
			if (r < l) swap(l, r); l++;
			if (l < 1) return -1;
			else {
				int mid=(l+r)/2,k=0;
				while (l+(1<<k)<=mid) k++;
				return min(MN[l][k],MN[r-(1<<k)+1][k]);
			}
		}
	};
	
	inline void GetHeight(){
		for (int i=1;i<=n;i++) if (rank[i] > 1){
			int sta = max(0, height[rank[i-1]]-1);
			int p1 = i+sta, p2 = sa[rank[i]-1]+sta;
			while (pat[p1++]==pat[p2++]) sta++;
			height[rank[i]] = sta;
		}
	}	
	
	inline void build(){
		memset(bot,0,sizeof(bot));
		memset(height,0,sizeof(height));
		tmp = t1; rank = t2;
		n = strlen(pat+1); m = SIGMA_SIZE;
		
		for (int i=1;i<=n;i++) bot[tmp[i]=pat[i]-'a'+1]++;
		for (int i=2;i<=m;i++) bot[i] += bot[i-1];
		for (int i=1;i<=n;i++) sa[bot[tmp[i]]--] = i;
		rank[sa[1]] = m = 1;
		for (int i=2;i<=n;i++) rank[sa[i]]=(tmp[sa[i]]==tmp[sa[i-1]])?m:++m;
		
		for (int k=1;k<=n;k*=2){ int T=0;
			for (int i=n-k+1;i<=n;i++) tmp[++T] = i;
			for (int i=1;i<=n;i++) if (sa[i]>k) tmp[++T] = sa[i]-k;
			for (int i=1;i<=m;i++) bot[i] = 0;
			for (int i=1;i<=n;i++) bot[rank[i]]++;
			for (int i=2;i<=m;i++) bot[i] += bot[i-1];
			for (int i=n;i;i--) sa[bot[rank[tmp[i]]]--] = tmp[i];
			
			swap(rank, tmp);
			rank[sa[1]] = m = 1;
			for (int i=2;i<=n;i++) if (tmp[sa[i-1]]==tmp[sa[i]] && tmp[sa[i-1]+k]==tmp[sa[i]+k]) rank[sa[i]] = m; else rank[sa[i]] = ++m; if (m >= n) break;
		}
		
		GetHeight();
		ST::build();
	}
	
	inline void solve(){
        Tm = 1; 
        for (int k=1;k<=n;k++){
            for (int i=1;i<=n;i+=k){ 
				int sta = ST::query(rank[i],rank[i+k]),T = sta / k + 1; 
				if (sta % k && ST::query(rank[i-k+sta%k],rank[i+sta%k]) >= k-sta%k) T++;
                if (T > Tm){Tm = T; len[cnt=1] = k;}
                if (T == Tm) {len[++cnt] = k;}
            } 
        }
        if (Tm==1) printf("Case %d: %c\n",++TT,pat[sa[1]]);
        else {
            for (int i=1;i<=n;i++) for (int j=1;j<=cnt;j++) {
				if (ST::query(i,rank[sa[i]+len[j]]) >= (Tm-1)*len[j]) {
                    pat[sa[i]+Tm*len[j]] = 0;
                    printf("Case %d: %s\n",++TT,pat+sa[i]); 
                    return;
                }   
			}
        }
    }
};

int main(){
	while (scanf("%s",pat+1) && pat[1] != '#'){
		SA::build();
		SA::solve();
	}
	return 0;
} 

【BZOJ 3998】[TJOI2015] 弦论

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3998
离线版题目:http://paste.ubuntu.com/18145026/
数据生成器:http://paste.ubuntu.com/18145048/

解题报告

这个,SAM求k小串的板题,然而我这个纸张写了两次、调了7h+才过QAQ     (浅谈模板写挂了的悲伤有多大.pdf
t==0 -> 后缀自动机的每一个节点只代表一个串
t==1 -> 后缀自动机的每一个节点代表|fail数的大小|个节点
然后顺着走一走就好。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;

const int MAXN = 1000000+9;
const int SGZ = 26;

char pat[MAXN];
int n,t; LL k;

namespace Suffix_Automaton{
	#define SAM Suffix_Automaton
	int ch[MAXN][SGZ],fail[MAXN],sz[MAXN],len[MAXN];
	int cnt,last,bot[MAXN],ord[MAXN]; LL cho[MAXN];
		
	inline void extend(int c){
		int w = last; last = ++cnt; 
		len[last] = len[w]+1; sz[last]=1;
		while (w && !ch[w]) ch[w] = last, w=fail[w];
		if (!w) fail[last] = 1;
		else {
			int nw = ch[w];
			if (len[nw] == len[w]+1) fail[last] = nw;
			else {
				int nnw = ++cnt; len[nnw] = len[w]+1;
				for (int i=0;i<SGZ;i++) ch[nnw][i]=ch[nw][i];
				fail[nnw] = fail[nw]; fail[nw] = fail[last] = nnw;
				while (w && ch[w]==nw) ch[w] = nnw, w = fail[w];
			}
		}
	}
	
	inline void build(){
		cnt = last = 1;
		n = strlen(pat+1);
		for (int i=1;i<=n;i++)
			extend(pat[i]-'a');
	}
	
	inline void sign(){
		for (int i=1;i<=cnt;i++) bot[len[i]]++;
		for (int i=1;i<=n;i++) bot[i] += bot[i-1];
		for (int i=cnt;i;i--) ord[bot[len[i]]--] = i;
		
		if (t == 1) for (int j=cnt,i=ord[j];j;i=ord[--j]) sz[fail[i]] += sz[i];
		else for (int i=2;i<=cnt;i++) sz[i] = 1;
		
		for (int j=cnt,i=ord[j];j;i=ord[--j]){
			cho[i] += (LL)sz[i];
			for (int c=0;c<SGZ;c++) 
				cho[i] += cho[ch[i]];
		}
	}
	
	inline void solve(){
		if (cho[1] < k) printf("-1\n");
		else {
			int w = 1; while (k > 0){
				for (int c=0;c<SGZ && k>0;c++) 
					if (cho[ch[w]] < k) k -= cho[ch[w]];
					else {putchar(c+'a');w=ch[w];k-=sz[w];break;}
			}
		}	
	}
};

int main(){
	scanf("%s%d%lld",pat+1,&t,&k);
	SAM::build();
	SAM::sign();
	SAM::solve();
	return 0;
}

【BZOJ 3238】[AHOI2013] 差异

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3238
离线版题目:http://paste.ubuntu.com/18084975/
数据生成器:http://paste.ubuntu.com/16074559/

这个东西,想了想,不会做QAQ于是可耻地看题解,果断SA算贡献
最近在做专题,又看到这个题,发现SAM算贡献不是更好算吗QAQ
贴一个SA的代码吧!SAM就偷懒不写啦!o( ̄▽ ̄)ブ

#include<iostream>
#include<cstdio>
#include<cstring>
#define LL long long
#define SA Suffix_Array
using namespace std;

const int MAXN = 1000000+9;

namespace Suffix_Array{
	int sa[MAXN],bot[MAXN],t1[MAXN],t2[MAXN],height[MAXN],*rank,*tmp;
	int n,m;
	
	inline void build(char *s){
		n = strlen(s+1); m = 26;
		rank = t1; tmp = t2;
		
		for (int i=1;i<=n;i++) bot[tmp[i]=s[i]-'a'+1]++;
		for (int i=2;i<=m;i++) bot[i] += bot[i-1];
		for (int i=1;i<=n;i++) sa[bot[tmp[i]]--] = i;
		rank[sa[1]] = m = 1;
		for (int i=2;i<=n;i++) rank[sa[i]] = s[sa[i]]==s[sa[i-1]]?m:++m;
		
		for (int k=1;k<=n;k*=2){
			int tot = 0; 
			for (int i=1;i<=m;i++) bot[i] = 0;
			for (int i=n-k+1;i<=n;i++) tmp[++tot] = i;
			for (int i=1;i<=n;i++) if (sa[i] > k) tmp[++tot] = sa[i] - k;
			for (int i=1;i<=n;i++) bot[rank[i]]++;
			for (int i=2;i<=m;i++) bot[i] += bot[i-1];
			for (int i=n;i;i--) sa[bot[rank[tmp[i]]]--] = tmp[i];
			
			swap(rank, tmp); rank[sa[1]] = m = 1;
			for (int i=2;i<=n;i++) if (tmp[sa[i]] == tmp[sa[i-1]] && tmp[sa[i]+k] == tmp[sa[i-1]+k]) rank[sa[i]] = m; else rank[sa[i]] = ++m; if (m >= n) break;
		}
	}
	
	inline void GetHeight(char *s){
		for (int i=1;i<=n;i++) if (rank[i] > 1) {
			int sta = max(0, height[rank[i-1]]-2);
			int p1 = i+sta, p2 = sa[rank[i]-1]+sta;
			while (s[p1++] == s[p2++]) sta++;
			height[rank[i]] = sta;
		}
	}
};

int L[MAXN],R[MAXN]; LL vout;
int cnt,que[MAXN],pos[MAXN]; 

inline void GetAns(){
	using namespace Suffix_Array;
	for(int i=1;i<=n;i++) vout += (LL)i*(LL)(n-1);
	
	cnt = 0;
	for (int i=1;i<=n;i++){ while (cnt && que[cnt] >= height[i]) cnt--;
		L[i] = pos[cnt]+1;
		que[++cnt] = height[i]; pos[cnt] = i;
	} 
	
	cnt = 0; pos[0] = n+1;
	for (int i=n;i;i--){
		while (cnt && que[cnt] > height[i]) cnt--;
		R[i] = pos[cnt]-1;
		que[++cnt] = height[i]; pos[cnt] = i;
	} 
	
	for (int i=1;i<=n;i++) vout -= 2LL*(LL)(i-L[i]+1)*(LL)(R[i]-i+1)*(LL)height[i];
	printf("%lld",vout);
}

char pat[MAXN];
int main(){
	scanf("%s",pat+1);
	SA::build(pat);
	SA::GetHeight(pat); 
	GetAns(); 
	return 0;
}

【HDU 5442】Favorite Donut

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5442
数据生成器:http://paste.ubuntu.com/18084370/

这题,SA可做,SAM可做,最小表示法可做
然而我的SA对拍怎么都过不了,本地不错,交上去就wa,弃疗

#include<iostream>
#include<cstdio> 
#include<cstring>
#include<algorithm>
using namespace std;

const int MAXN = 1600000+9;
const int SIGMA_SIZE = 30;

char pat[MAXN];
int n;

namespace Suffix_Array{
	#define SA Suffix_Array
	int sa[MAXN],height[MAXN],bot[MAXN];
	int t1[MAXN],t2[MAXN],*tmp,*rank;
	int m,cnt,Pos[MAXN],ord[MAXN],wor[MAXN];
	
	inline void GetHeight(){
		for (int i=1;i<=n;i++) height[i] = 0;
		for (int i=1;i<=n;i++) if (rank[i] > 1){
			int sta = max(0,height[rank[i-1]]-2);
			int p1 = i+sta, p2 = sa[rank[i]-1]+sta;
			while (pat[p1++] == pat[p2++]) sta++;
			height[rank[i]] = sta;
		}
	}
	
	inline void build(){
		memset(bot,0,sizeof(bot));
		memset(height,0,sizeof(height));
		
		tmp = t1; rank = t2;
		n = strlen(pat+1); m = SIGMA_SIZE;
		for (int i=1;i<=n;i++) bot[tmp[i]=pat[i]-'a'+2]++;
		for (int i=2;i<=m;i++) bot[i] += bot[i-1];
		for (int i=1;i<=n;i++) sa[bot[tmp[i]]--] = i;
		rank[sa[1]] = m = 1;
		for (int i=2;i<=n;i++)
			if (tmp[sa[i]]==tmp[sa[i-1]]) rank[sa[i]] = m;
			else rank[sa[i]] = ++m;
			
		for (int k=1;k<=n;k*=2){int T=0;
			for (int i=n-k+1;i<=n;i++) tmp[++T] = i;
			for (int i=1;i<=n;i++) if (sa[i]>k) tmp[++T] = sa[i]-k;
			for (int i=1;i<=m;i++) bot[i] = 0;
			for (int i=1;i<=n;i++) bot[rank[i]]++;
			for (int i=2;i<=m;i++) bot[i] += bot[i-1];
			for (int i=n;i;i--) sa[bot[rank[tmp[i]]]--] = tmp[i];
			
			swap(rank, tmp);
			rank[sa[1]] = m = 1;
			for (int i=2;i<=n;i++) if (tmp[sa[i]]==tmp[sa[i-1]] && tmp[sa[i]+k]==tmp[sa[i-1]+k]) rank[sa[i]] = m; else rank[sa[i]] = ++m; if (m >= n) break;
		}
		
		GetHeight();
	}
	
	inline bool sort_cmp(const int a, const int b){
		if (Pos[a] != Pos[b]) return Pos[a] < Pos[b];
		else return wor[a] < wor[b]; } inline bool judge(int i, int pos){ if ((pos>=1&&pos<=n/4) || (pos>=n/2+2&&pos<=n/2+n/4+1)){ cnt=1; if (pos>=1&&pos<=n/4) Pos[cnt]=pos, wor[cnt]=0; else Pos[cnt]=n/2+n/4+2-pos, wor[cnt]=1; while (height[i--] >= n/4){
				pos = sa[i];
				if (pos>=1&&pos<=n/4) Pos[++cnt]=pos, wor[cnt]=0; else if (pos>=n/2+2&&pos<=n/2+n/4-1) Pos[++cnt]=n/2+n/4+2-pos, wor[cnt]=1;
			}
			
			for (int j=1;j<=cnt;j++) ord[j] = j; sort(ord+1,ord+1+cnt,sort_cmp); printf("%d %d\n",Pos[ord[1]],wor[ord[1]]); return true; } else return false; } inline void solve(){ for (int i=n;i;i--) if (judge(i,sa[i])) break; } }; int main(){ int T; cin>>T;
	while (T--){
		scanf("%d%s",&n,pat+1); 
		for (int i=1;i<=n;i++) pat[n+i] = pat[i]; n *= 2;
		for (int i=1;i<=n;i++) pat[n+i+1] = pat[n-i+1];
		pat[n+1] = 'a'-1; n=n*2+1; pat[n+1] = 0;
		SA::build();
		SA::solve();
	}	
	return 0;
}