题解 [ICPC2021 沈阳] String Problem

传送门

首先发现要求的子串的右边界是来搞笑的,最大字典序子串一定是这个前缀的一个后缀
然后考虑怎么求出一个前缀的字典序最大的子串的开始位置
第一反应是 SA,但是涉及到了这个前缀之后的比较而不可行
那么考虑这个前缀的每个后缀中,有哪些是可能在加入一些字符后成为新的最大字典序字符的呢?
发现一定是当前字典序最大的后缀的一个前缀
那么考虑动态维护这些前缀
一个 \(O(n^2)\) 的维护方法是每次比较这些前缀中相邻两个加入一个字符后的大小关系
然后优化,发现可以二分+hash预处理相邻两个前缀在哪一次加入字符时会不再满足一个是另一个前缀的性质
那么就在那个时候将字典序小的删掉就好了
加入和删除的时候用类似动态凸包的方式维护
复杂度 \(O(n\log n)\)

upd:貌似有常数更小的写法
“一个后缀是当前字典序最大的后缀的一个前缀”即“一个后缀是当前字典序最大的后缀的 boader”
那么一个串的 boader 形成 log 个等差数列
等差数列中的每个 boader 是由原串去掉若干个周期形成的
那么在同一个等差数列中的 boader 的下一个字符相等
这样每次就只需要考虑 log 个等差数列的每一个中最大的 boader

然后题解做法:
艹我打不开官方题解
一个 SAM 上的贪心做法:
对原串建 SAM,贪心跑字典序最大的串
因为 SAM 是 DAG,且两条路径要么终点不同要么长度不同
所以第一次到一个节点的路径即为字典序最大的后缀
对每个对应了原串的某个前缀的节点记录这个东西即可

另一个 SAM 做法
还是考虑要求的是一个每个前缀的字典序最大后缀
一个后缀对应的是这个前缀的 SAM 上的一条根到 now 的路径
考虑在加入一个字符后维护出新的路径
发现加入一个字符后会使一条链上的点多一条出边
考虑原来的路径上多了一条出边,且这条出边对应的字符是这个点出边中最大的那个的点
那么从这个点拐到新加的边上可以得到一条字典序最大的路径
若有多个这样的点,选长度最短的即可
那么新的路径就是从这个点拐向新的出边,再走到新的 now 形成的路径

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

int n;
char str[N];
int tim[N], now;
ull pw[N], h[N];
set<int> s, del[N];
const ull base=13131;
inline ull hashing(int l, int r) {return h[r]-h[l-1]*pw[r-l+1];}

void match(int a, int b) {
	// cout<<"match: "<<a<<' '<<b<<endl;
	int l=0, r=n-max(a, b)+1, mid;
	while (l<=r) {
		mid=(l+r)>>1;
		if (hashing(a, a+mid-1)==hashing(b, b+mid-1)) l=mid+1;
		else r=mid-1;
	}
	// cout<<"cmp: "<<int(str[a+l-1])<<' '<<int(str[b+l-1])<<endl;
	if (str[a+l-1]>str[b+l-1]) swap(a, b);
	if (tim[a]) del[tim[a]].erase(a);
	del[tim[a]=max(max(a, b)+l-1, now)].insert(a); //, cout<<1<<endl;
	// cout<<"del "<<a<<" at "<<tim[a]<<endl;
}

void erase(int id) {
	// cout<<"erase: "<<id<<endl;
	auto x=s.erase(s.find(id));
	if (x==s.end()||x==s.begin()) return ;
	auto y=x--;
	match(*x, *y);
}

void ins(int id) {
	auto it=s.insert(id).fir;
	if (it==s.begin()) return ;
	match(*--it, id);
}

signed main()
{
	scanf("%s", str+1);
	n=strlen(str+1);
	pw[0]=1;
	for (int i=1; i<=n; ++i) pw[i]=pw[i-1]*base;
	for (int i=1; i<=n; ++i) h[i]=h[i-1]*base+str[i];
	for (int i=1; (now=i)<=n; ++i) {
		// cout<<"i: "<<i<<endl;
		// cout<<"del: "; for (auto it:del[i]) cout<<it<<' '; cout<<endl;
		ins(i);
		while (del[i].size()) {int t=*del[i].begin(); del[i].erase(t); erase(t);}
		printf("%d %d\n", *s.begin(), i);
		// cout<<endl;
	}
	
	return 0;
}
posted @ 2022-04-28 10:31  Administrator-09  阅读(2)  评论(0编辑  收藏  举报