题面
解析
题意就是求字符串的最小表示, 当然可以用O(n)的算法快速求出,码量也不大,但最近讲了SAM,要求我们用SAM做这道题, 那就用SAM做一波
预处理还是一样,先要复制一遍原字符串,然后建立一个后缀自动机,从根节点开始,用贪心的思路优先选取最小的节点,然后向后跳,跳n步后就是答案, 这里一定可以跳n步,因为如果一开始是在后面区间内开始跳的话,假设这个点是n+p, 那在p点一定可以跳n步, 并且同时保证了字典序不变
还有一个问题, 知道了序列,如何求出答案?当然不能O(n2)暴力求出, 这里有这个字符串的性质,如果我们的终止节点为q, 假如q点代表的right集合内只有一个点,那么q的len就是这个点的前缀长度,答案就是len-n+1; 如果q点代表的right集合内有不止1个点,容易知道这几个点的right一定在右半区间,并且以这个集合内的几个点为右端点的长度为n的字符串相同, 那么这个字符串存在循环节,这几个端点的前n个字符串相同,由于循环节的存在,最靠左的端点的前缀一定是其他端点的前缀的后缀,那么q点的len就是最靠左的端点的前缀的长度, 答案就是len-n+1。
综上, 答案就是len[q] - n + 1
代码:
#include<cstdio> #include<cstring> using namespace std; typedef long long ll; const int maxn = 10005 ; int T, n, las, cnt; char s[maxn]; struct point{ int ch[26], len, fa; }d[maxn<<2]; void Add(int x) { int p = las, np = las = ++cnt; d[np].len = d[p].len + 1; for( ; p && !d[p].ch[x] ; p = d[p].fa) d[p].ch[x] = np; if(!p) d[np].fa = 1; else { int q = d[p].ch[x]; if(d[q].len == d[p].len + 1) d[np].fa = q; else { int nq = ++cnt; d[nq] = d[q]; d[nq].len = d[p].len + 1; d[q].fa = d[np].fa = nq; for( ; p && d[p].ch[x] == q; p = d[p].fa) d[p].ch[x] = nq; } } } void work() { int now = 1; for(int i = 1 ; i <= n; ++i) for(int j = 0; j < 26; ++j) if(d[now].ch[j]) { now = d[now].ch[j]; break; } printf("%d ", d[now].len - n + 1); } int main() { scanf("%d", &T); while(T--) { las = cnt = 1; memset(d,0,sizeof(d)); scanf("%s", s+1); n = strlen(s+1); for(int i = 1; i <= n; ++i) Add(s[i]-'a'); for(int i = 1; i <= n; ++i) Add(s[i]-'a'); work(); } return 0; }