题解 [CCPC2021 桂林] Suffix Automaton

传送门

好麻烦.jpg

首先容易想到 SAM 求出每种长度的本质不同子串个数
然后问题就变成了求某种长度的字典序第 \(k\) 大子串
发现每个节点代表了长度在某个区间内的一些子串
所以扫描线可以知道每个长度的子串的有效终止节点
然后就不会了

正解(做法 1)与之类似
考虑上面做法卡在哪里了:对一些节点按它们代表的长度为 \(len\) 的字典序排序

  • 关于子串字典序问题:
    一个经典做法是对反串建后缀树,一个节点与之父亲的边的边权是 这个节点恰好比父节点代表的最大串长度大 1 的串比父节点那个串多的字符
    这样若 dfs 时出边按字典序访问,则每个节点都是按原串字典序访问的

于是按上面说的做,发现就是这个长度的合法节点中 dfs 序第 \(k\)
线段树/树状数组二分即可

做法 2:
也可以用 SA
长度为 \(len\) 的本质不同子串数是 \(\tt{height} \geqslant len\) 的个数减去长度 \(< \tt len\) 的后缀个数
对于长度 len,令满足上面限制的字典序后缀集合为 \(s\),其中 \(s\) 中的元素均为后缀数组中的下标
而那些本质相同的长为 len 的子串是 \([s_i, s_{i+1})\) 对应的后缀
那么最靠左的就是后缀数组中这个区间的最小值
同样扫描线对每个 len 维护出合法的后缀集合,树状数组上二分+RMQ即可
复杂度 \(O(n\log n)\)记得开 \(\tt long long\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 1000010
#define fir first
#define sec second
#define pb push_back
#define ll long long
// #define int long long

ll k;
int n, q;
char s[N];
ll sum[N];
vector<int> suf[N];
pair<int, int> ans[N];
set<pair<ll, int>> table;
vector<pair<int, int>> que[N];
int sa[N], rk[N], oldrk[N<<1], cnt[N], id[N], px[N], ht[N], siz[N], lg[N], st[21][N], bit[N], m=256;
inline bool cmp(int a, int b, int w) {return oldrk[a]==oldrk[b]&&oldrk[a+w]==oldrk[b+w];}
inline int qmin(int l, int r) {int t=lg[r-l+1]-1; return min(st[t][l], st[t][r-(1<<t)+1]);}
inline void add(int i) {for (; i<=n+1; i+=i&-i) ++bit[i];}
inline void del(int i) {for (; i<=n+1; i+=i&-i) --bit[i];}
inline int kth(int k) {
	int ans=0;
	for (int i=lg[n+1]-1; ~i; --i) if ((ans|(1<<i))<=n+1 && k>bit[ans|(1<<i)])
		ans|=1<<i, k-=bit[ans|(1<<i)];
	return ++ans;
}
// inline void add(int i) {++bit[i];}
// inline void del(int i) {--bit[i];}
// inline int kth(int k) {for (int i=1; ; ++i) if (!(k-=bit[i])) return i;}

signed main()
{
	scanf("%s%d", s+1, &q);
	n=strlen(s+1);
	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=1; i<=n; ++i) sa[cnt[rk[i]]--]=i;
	for (int w=1,p; ; w<<=1,m=p) {
		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;
		for (int i=0; i<=m; ++i) cnt[i]=0;
		for (int i=1; i<=n; ++i) ++cnt[px[i]=rk[id[i]]];
		for (int i=1; i<=m; ++i) cnt[i]+=cnt[i-1];
		for (int i=n; i; --i) sa[cnt[px[i]]--]=id[i];
		for (int i=1; i<=n; ++i) oldrk[i]=rk[i];
		p=0;
		for (int i=1; i<=n; ++i) rk[sa[i]]=cmp(sa[i], sa[i-1], w)?p:++p;
		if (p==n) break;
	}
	for (int i=1,k=0; i<=n; ++i) {
		if (k) --k;
		while (s[i+k]==s[sa[rk[i]-1]+k]) ++k;
		ht[rk[i]]=k;
	}
	for (int i=1; i<=n+1; ++i) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	for (int i=1; i<=n; ++i) ++siz[ht[i]+1], suf[ht[i]+1].pb(i);
	for (int i=1; i<=n; ++i) siz[i]+=siz[i-1]-(i!=1);
	for (int i=1; i<=n; ++i) table.insert({sum[i]=sum[i-1]+siz[i], i});
	// cout<<"siz: "; for (int i=1; i<=n; ++i) cout<<siz[i]<<' '; cout<<endl;
	for (int i=1; i<=n; ++i) st[0][i]=sa[i];
	for (int i=1; i<=lg[n]-1; ++i)
		for (int j=1,len=1<<i-1; j+(1<<i)-1<=n; ++j)
			st[i][j]=min(st[i-1][j], st[i-1][j+len]);
	for (int i=1,t; i<=q; ++i) {
		scanf("%lld", &k);
		if (k>sum[n]) ans[i]={-1, -1};
		else {
			t=table.lower_bound({k, 0})->sec;
			que[t].pb({k-sum[t-1], i});
			// cout<<"add "<<k<<" to "<<t<<endl;
		}
	}
	add(n+1);
	for (int i=1; i<=n; ++i) {
		// cout<<"i: "<<i<<endl;
		// cout<<"suf: "; for (auto it:suf[i]) cout<<it<<' '; cout<<endl;
		for (auto it:suf[i]) add(it);
		if (i>1) del(rk[n-i+2]);
		for (auto it:que[i]) {
			int l=kth(it.fir), r=kth(it.fir+1)-1, pos=qmin(l, r);
			ans[it.sec]={pos, pos+i-1};
		}
	}
	for (int i=1; i<=q; ++i) printf("%d %d\n", ans[i].fir, ans[i].sec);
	
	return 0;
}
posted @ 2022-04-28 17:13  Administrator-09  阅读(3)  评论(0编辑  收藏  举报