海亮01/08字符串专题

海亮01/08字符串

题单链接

SAM学习笔记

T1

CF235C

题意

给定一个主串\(S\)\(n\)个询问串,求每个询问串的所有循环同构在主串中出现的次数总和。

题解

如果不问循环同构,只问正常询问串的出现次数,会吗?

直接上板子即可对叭?

那么对于循环同构,我们可以直接加长一倍(加到 \(2n-1\),如果是 \(2n\) 就有一个重复了),那么新串所有长度为 \(n\) 的子串就可以代表所有循环同构。

然后对这样一个新串进行询问,每次添加一个字符找到最长的有这个字符转移的状态,同时更新当前已查找的子串长度。

然后如果现在已查找的子串长度超过 \(n\) 怎么办?直接暴力跳 \(link\) 树直到当前节点的长度区间包含 \(n\)。然后统计次数。

同时为了解决重复计数的问题,查询之后给这个状态打个标记,下次不统计即可,然后别忘了一个询问做完了清空标记。

Accept=Happy New Year! 好评

代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x = 0, f = 1;char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
	return x * f;
}
const int maxn = 1e6 + 10;
char ch[maxn];
bool book[maxn * 2];
struct SAM{
	struct node{
		int link, len, siz;
		unordered_map<char,int> nxt;
	}d[maxn * 2];
	int a[maxn * 2], cf[maxn], tot, last;
	void build(){tot = last = 0;d[0].link = -1;}
	void insert(char ch){
		int cur = ++tot;d[cur].siz = 1;
		d[cur].len = d[last].len + 1;
		int p = last;
		while(p != -1 && !d[p].nxt[ch]){d[p].nxt[ch] = cur;p = d[p].link;}
		if(p == -1){d[cur].link = 0;}
		else{
			int q = d[p].nxt[ch];
			if(d[p].len + 1 == d[q].len){d[cur].link = q;}
			else{
				int clone = ++tot;
				d[clone].link = d[q].link;
				d[clone].nxt = d[q].nxt;
				d[clone].len = d[p].len + 1;
				while(p != -1 && d[p].nxt[ch] == q){d[p].nxt[ch] = clone;p = d[p].link;}
				d[q].link = d[cur].link = clone;
			}
		}
		last = cur;
	}
	void build(char *s){
		build();
		int len = strlen(s + 1);
		for(int i = 1;i <= len;i++)insert(s[i]);
		for(int i = 0;i <= tot;i++)cf[d[i].len]++;cf[0] = 0;
		for(int i = 1;i <= len;i++)cf[i] += cf[i - 1];
		for(int i = 0;i <= tot;i++)a[cf[d[i].len]--] = i;
		for(int i = tot;i;i--){int p = a[i];d[d[p].link].siz += d[p].siz;}
	}
	int query(char *s){
		int ans = 0;
		int p = 0, len = strlen(s + 1), dep = 0;
		vector<int> vec;vec.clear();
		for(int i = 1;i < len * 2;i++){
			char ch = s[(i - 1) % len + 1];
			while(p != -1 && !d[p].nxt[ch]){p = d[p].link;dep = d[p].len;}
			if(p != -1){
				p = d[p].nxt[ch];dep++;
				while(dep > len){dep--;if(dep <= d[d[p].link].len)p = d[p].link;}
				if(dep >= len && !book[p]){ans += d[p].siz;book[p] = 1;vec.push_back(p);}
			}
			else p = 0;
		}
		for(int u : vec)book[u] = false;
		return ans;
	}
}sam;
signed main(){
	scanf("%s",ch + 1);sam.build(ch);
	for(int T = read();T;T--){
		scanf("%s",ch + 1);
		printf("%d\n",sam.query(ch));
	}
	return 0;
}

T2

T3

P4094

题意

佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了一个长为 \(n\) 的字符串 \(s\),和 \(m\) 个问题。佳媛姐姐必须正确回答这 \(m\) 个问题,才能打开箱子拿到礼物,升职加薪,出任 CEO,嫁给高富帅,走上人生巅峰。

每个问题均有 \(a,b,c,d\) 四个参数,问你子串 \(s[a..b]\) 的所有子串和 \(s[c..d]\) 的最长公共前缀的长度的最大值是多少?佳媛姐姐并不擅长做这样的问题,所以她向你求助,你该如何帮助她呢?

题解

如果找最大值发现根本做不了。

然后发现答案满足单调性。

然后变成判定 \(s[c,c+mid-1]\) 是不是 \(s[a,b]\) 的子串。

先在 SAM 上找到代表 \(s[1,c+mid-1]\) 的节点,然后一直跳 \(link()\) 直到 \(mid\in[len(link(p))+1,len(p)]\),我们就找到了代表 \(s[c,c + mid-1]\) 的等价类。

然后我们需要找在这个等价类的 \(endpos\) 集合中,是不是包含了 \([a+mid-1,b]\) 中的其中一个数,这个可以用线段树动态开点+合并解决。

然后就没了。

代码

#include<bits/stdc++.h>
using namespace std;
bool stmemory;
inline int read(){
	int x = 0, f = 1;char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
	return x * f;
}
const int maxn = 3e5 + 10;
int n, m;
int fa[21][maxn * 2];
char ch[maxn];
vector<int> edg[maxn * 2];

int head[maxn * 2], id[maxn];
struct Segment_Tree{
	int tot;
	struct node{
		int lc, rc;int sum;
		node(int sum = 0,int lc = 0,int rc = 0):sum(sum),lc(lc),rc(rc){}
	}d[maxn * 30];
	void pushup(int p){d[p].sum = d[d[p].lc].sum + d[d[p].rc].sum;}
	void insert(int l,int r,int &p,int pos){
		if(!p)p = ++tot; d[p].sum++;
		if(l == r)return; int mid = l + r >> 1;
		if(pos <= mid)insert(l,mid,d[p].lc,pos);
		else insert(mid + 1,r,d[p].rc, pos);
		pushup(p);
	}
	int query(int l,int r,int s,int t,int p){
		if(!p)return 0;
		if(s <= l && r <= t)return d[p].sum;
		int mid = l + r >> 1, ans = 0;
		if(s <= mid)ans += query(l,mid,s,t,d[p].lc);
		if(mid < t)ans += query(mid + 1,r,s,t,d[p].rc);
		return ans;
	}
	int merg(int l,int r,int p,int q){
		if(!p || !q)return p | q;
		int x = ++tot;int mid = l + r >> 1;
		d[x].sum = d[p].sum + d[q].sum;
		if(l == r)return x;
		d[x].lc = merg(l,mid,d[p].lc,d[q].lc);
		d[x].rc = merg(mid + 1,r,d[p].rc,d[q].rc);
		return x;
	}
}tree;

struct SAM{
	struct node1{
		int link, len;
		unordered_map<char,int> nxt;
	}d[maxn * 2];
	int tot, last;
	void build(){tot = last = 0;d[0].link = -1;}
	void insert(char ch){
		int cur = ++tot;
		d[cur].len = d[last].len + 1;
		int p = last;
		while(p != -1 && !d[p].nxt[ch]){d[p].nxt[ch] = cur;p = d[p].link;}
		if(p == -1){d[cur].link = 0;}
		else{
			int q = d[p].nxt[ch];
			if(d[p].len + 1 == d[q].len){d[cur].link = q;}
			else{
				int clone = ++tot;
				d[clone].link = d[q].link;
				d[clone].nxt = d[q].nxt;
				d[clone].len = d[p].len + 1;
				while(p != -1 && d[p].nxt[ch] == q){d[p].nxt[ch] = clone;p = d[p].link;}
				d[q].link = d[cur].link = clone;
			}
		}
		last = cur;
	}
	void dfs(int u,int f){
		for(int i = 1;i <= 20;i++){fa[i][u] = (fa[i - 1][u] == -1 ? -1 : fa[i - 1][fa[i - 1][u]]);}
		for(int v : edg[u])
			if(v != f){
				fa[0][v] = u;dfs(v, u);
				head[u] = tree.merg(1,n,head[u],head[v]);
			}
	}
	void init(char *s){
		int len = strlen(s + 1);
		id[0] = 0;build();
		for(int i = 1;i <= len;i++){
			insert(s[i]);id[i] = last;
			tree.insert(1,len,head[last],i);
		}
		for(int i = 1;i <= tot;i++){
			edg[d[i].link].push_back(i);
			edg[i].push_back(d[i].link);
		}
		fa[0][0] = -1;dfs(0, -1);
	}
}sam;
bool check(int mid,int a,int b,int c,int d){
	if(mid == 0)return 1;
	int p = id[c + mid - 1];
	for(int i = 20;i + 1;i--){if(fa[i][p] != -1 && sam.d[fa[i][p]].len >= mid)p = fa[i][p];}
	return tree.query(1,n,a + mid - 1,b,head[p]) > 0;
}
bool edmemory;
signed main(){
	cerr << (&stmemory - &edmemory) / 1024.0 / 1024.0 << "Mib cost." << endl;
	n = read(); m = read();
	scanf("%s",ch + 1); sam.init(ch);
	while(m--){
		int a = read(), b = read(), c = read(), d = read();
		int l = 0, r = min(b - a + 1,d - c + 1), ans = 0;
		while(l <= r){
			int mid = l + r >> 1;
			if(check(mid,a,b,c,d)){ans = mid,l = mid + 1;}
			else r = mid - 1;
		}
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2024-01-08 16:34  Call_me_Eric  阅读(23)  评论(0)    收藏  举报
Live2D