SAM 从入门到入土

1 CF204E
求n个字符串的一个字符串集合,对于每个字符串求有多少个子串是这个字符串集合中至少k个字符串的子串。

做法

我们进行线段树合并出每个 状态所包含的是哪个串 统计出是否大于k
如果 大于k我们直接 加上len的贡献 如果没有标记的话就是 最近的有标记的父亲

2 CF666E
给出一个串s
和几个串ti
求出串s [l,r] 在串 ti出现次数最大
相同输出最左边的

做法

线段树合并 每个节点代表的是 该点 在哪个字符串中出现(出现的位置) 并且维护出出现的次数 那么 我们进行合并的时候考虑二维就可以了
我们可以倍增锁定位置

3 P3975 [TJOI2015]弦论

做法

我们分情况讨论
如果t=0
那么就是本质不同的串 我们每个点的点权为 1
如果t=1
包含位置不同的串 我们每个点的点权可以dfs 或者基数排序求
我们dfs(也可以dag上dp) 一遍 求出 每个状态和 表示 点u能到达的点的数量
那么我们最后递归处理的时候 如果k<=siz[1] 直接返回-1
否则我们贪心的递归输出

4 P4070 [SDOI2016]生成魔咒

做法

每增加一个串 就会增加 len[np]-len[tr[np].fa] 个字符串
5CF235C Cyclical Quest

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

做法

很套路的东西 我们可以直接在t串后面接一个串 然后我们跑t串 每次倍增
找到 加入 t[i] 后 长度为l的状态 直接给该状态 打上标记 加上该状态贡献
注意如果 没有找到 下一个状态记得将 该指针置为1

6P4094 [HEOI2016/TJOI2016]字符串

做法

给定串s 和q 个询问
每次问s[a…b]的所有子串和s[c…d]的最长公共前缀最大值
将串反转处理 那么我们每次可以二分出一个mid 倍增可以找到s[i-mid+1,i]
预处理的话线段树合并求出每个点的endpos集合
那么我们只需要判断一下endpos集合里包含 [a+mid-1,b]不是[a,b]
如果小于a+mid-1 那么这个字符串的左端有可能小于a 就错了
还有线段树合并的时候如果 不建立新节点的写法可能会tle

7葫芦的考验之定位子串2.0
求子串 s[l ,r] 在 s[x,y]的出现次数

做法

上个题的降维版本
线段树合并预处理endpos
我们倍增可以锁定 s[l,r]的状态
直接查询 [x+mid-1,y]

//完整代码
#include <bits/stdc++.h>
using namespace  std;
//#define  int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
#define fi first
#define se second
#define pb  push_back
#define inf 1ll<<62
#define endl "\n"
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define de_bug(x) cerr << #x << "=" << x << endl
#define all(a) a.begin(),a.end()
#define IOS   std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define  fer(i,a,b)  for(int i=a;i<=b;i++)
#define  der(i,a,b)  for(int i=a;i>=b;i--)
const int mod = 1e9 + 7;
const int N = 4e5 + 10;
int n, m , k;
string s;
int cnt = 1;
int last = 1;
int f[N];
int fa[N][21];
struct node {
	int fa, len;
	int son[26] = {};
} tr[N];
map<int, int>mp;

int siz[N];
int rt[N<<5];
int ls[N<<5];
int rs[N<<5];
int sum[N<<5];
int idx1;
int idx;
struct seg {
	void pushup(int x) {
		sum[x] = sum[ls[x]] + sum[rs[x]];
	}
	void change(int& i, int l, int r, int x) {
		if (!i) i = ++idx;
		//	de_bug(idx);
		if (l == r) {
			sum[idx] =1;
			return;
		}
		int mid = l + r >> 1;
		if (x <= mid)
			change(ls[i], l, mid, x);
		else
			change(rs[i], mid + 1, r, x);
		pushup(i);
	}
	int query(int i, int l, int r, int ql, int qr) {
		if (!i) return 0;
		if (ql <= l && r <= qr) {
			return sum[i];
		}
		int mid = l + r >> 1;
		int ans = 0;
		if (ql <= mid) ans += query(ls[i], l, mid, ql, qr);
		if (qr > mid) ans += query(rs[i], mid + 1, r, ql, qr);
		return ans;
	}
	int merge(int x, int y, int l, int r) {
		if (!x || !y) return x + y;
		int p = ++idx;
		int mid = l + r >> 1;
		if (l == r) {
			sum[p] = sum[x] +sum[y];
			return p;
		}
		ls[p] = merge(ls[x], ls[y], l, mid);
		rs[p] = merge(rs[x], rs[y], mid + 1, r);
		pushup(p);
		return p;
	}
} T;
void extend (int c) {
	int p = last, np = last = ++cnt;
	tr[np].len = tr[p].len + 1;
	T.change(rt[last],1,n,tr[last].len);
	mp[++idx1] = np;
	siz[np] = 1;
	for(; p && !tr[p].son[c];  p = tr[p].fa) tr[p].son[c] = np;
	if(!p) tr[np].fa = 1;
	else {
		int q = tr[p].son[c];
		if(tr[q].len == tr[p].len + 1)tr[np].fa = q;
		else {
			int nq = ++cnt;
			tr[nq] = tr[q];
			tr[nq].len = tr[p].len + 1;
			tr[q].fa = tr[np].fa = nq;
			for(; p && tr[p].son[c] == q; p = tr[p].fa) tr[p].son[c] = nq;
		}
	}
}
vi g[N];

void dfs(int u, int ff) {
	fa[u][0] = ff;
	for(int i = 1; i <= 20; i++) {
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
	}
	for(auto v : g[u]) {
		if(v == ff)continue;
		dfs(v, u);
		rt[u]=T.merge(rt[u],rt[v],1,n);
	}
}
int query(int l, int r,int a,int b) {
	int len = r - l + 1;
	int ans = mp[r];
	for(int i = 20; i >= 0; i--) {
		if(tr[fa[ans][i]].len >= len) {
			ans = fa[ans][i];
		}
	}
	return  T.query(rt[ans],1,n,a+r-l,b);
}
void solve() {
	cin >> s;
	n=s.size();

	for(int i=1; i<=n; i++) {
		extend(s[i-1] - 'a');
	}
	for(int i = 2; i <= cnt; i++) {
		g[tr[i].fa].push_back(i);
	}
	dfs(1,0);
	cin >> k;
	fer(i, 1, k) {
		int a, b,l,r;
		cin >> l >> r>>a>>b;
		cout << query(l, r,a,b) << endl;
	}
}
int main() {
	IOS;
	int _ = 1;
	//cin>>_;
	while( _-- )
		solve();
}


8"蔚来杯"2022牛客暑期多校训练营3H
板子题
贴代码了
t串在s串上跑
直接st查询最小值 即可

#include <bits/stdc++.h>
using namespace  std;
//#define  int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
#define fi first
#define se second
#define pb  push_back
#define inf 1ll<<62
#define endl "\n"
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define de_bug(x) cerr << #x << "=" << x << endl
#define all(a) a.begin(),a.end()
#define IOS   std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define  fer(i,a,b)  for(int i=a;i<=b;i++)
#define  der(i,a,b)  for(int i=a;i>=b;i--)
const int mod = 1e9 + 7;
const int N = 1e6 + 10;
int n, m , k;
string s;
int cnt = 1;
int last = 1;

struct node {
	int fa, len;
	int son[26] = {};
} tr[N];
map<int, int>mp;
int idx;
int siz[N];
void extend (int c) {
	int p = last, np = last = ++cnt;
	tr[np].len = tr[p].len + 1;
	mp[++idx] = np;
	siz[np] = 1;
	for(; p && !tr[p].son[c];  p = tr[p].fa) tr[p].son[c] = np;
	if(!p) tr[np].fa = 1;
	else {
		int q = tr[p].son[c];
		if(tr[q].len == tr[p].len + 1)tr[np].fa = q;
		else {
			int nq = ++cnt;
			tr[nq] = tr[q];
			tr[nq].len = tr[p].len + 1;
			tr[q].fa = tr[np].fa = nq;
			for(; p && tr[p].son[c] == q; p = tr[p].fa) tr[p].son[c] = nq;
		}
	}
}
ll mi[N][20];
ll sum[N];
int lg[N];

ll query(int l, int r) {
	int t = lg[r - l + 1];
	return min(mi[l][t], mi[r - (1 << t) + 1][t]);
}
void solve() {
	for(int i = 2; i < N; i++) {
		lg[i] = lg[i >> 1] + 1;
	}
	cin >> n >> m >> k;
	string s;
	cin >> s;
	for(auto c : s) {
		extend(c - 'a');
	}
	for(int i = 1; i <= m; i++) {
		cin >> sum[i];
		sum[i] += sum[i - 1];
		mi[i][0] = sum[i - 1];
	}

	for(int i = 1; i <= 20; i++)
		for(int j = 1; j + (1 << i) - 1 <= m; j++) {
			mi[j][i] = min(mi[j][i - 1], mi[j + (1 << (i - 1))][i - 1]);
		}
	while(k--) {
		string t;
		cin >> t;
		ll ans = 0;
		int p = 1;
		int l = 0;
		for(int i = 0; i < m; i++) {
			while(p && !tr[p].son[t[i] - 'a']) {
				p = tr[p].fa;
				l = tr[p].len;
			}
			if(tr[p].son[t[i] - 'a']) {
				l++;
				p = tr[p].son[t[i]-'a'];
			} else  l = 0, p = 1;

			if(l)
				ans = max(ans, sum[i + 1] - query(i - l + 2, i + 1));
		}
		cout << ans << endl;
	}
}
int main() {
	IOS;
	int _ = 1;
	//cin>>_;
	while( _-- )
		solve();
}



9CF802I Fake News (hard)
sam板子 直接求出每个状态的大小那么 这个状态的贡献就是 siz[u]*siz[u]*len[u]-len[tr[u].fa]
10CF1037H Security
线段树合并 求个每个状态的endpos集合
因为字典序要严格大于t串
匹配t串 求出在S[L,R]出现过的最长串的状态
然后我们贪心的考虑 如果刚好匹配成功 那么 我们只需下一位从 0开始就行
否则我们用一个栈去维护 从后往前 依次 去枚举 比当前位大并且 出现在s[l,r]
的串

11 21 沈阳String Problem
贪心走 字典序大的 第一走到的肯定是最大后缀

#include <bits/stdc++.h>
using namespace  std;
//#define  int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
#define fi first
#define se second
#define pb  push_back
#define inf 1ll<<62
#define endl "\n"
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define de_bug(x) cerr << #x << "=" << x << endl
#define all(a) a.begin(),a.end()
#define IOS   std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define  fer(i,a,b)  for(int i=a;i<=b;i++)
#define  der(i,a,b)  for(int i=a;i>=b;i--)
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int n, m , k;
string  s;
string  t;
struct node {
	int len, fa;
	int son[26];
	int pos;
} tr[N * 2];
int cnt = 1;
int last = 1;
int ans[N];
int pos[N];

void extend (int c) {
	int p = last, np = last = ++cnt;
	tr[np].pos = tr[np].len = tr[p].len + 1;
	for(; p && !tr[p].son[c];  p = tr[p].fa) tr[p].son[c] = np;
	if(!p) tr[np].fa = 1;
	else {
		int q = tr[p].son[c];
		if(tr[q].len == tr[p].len + 1)tr[np].fa = q;
		else {
			int nq = ++cnt;
			tr[nq] = tr[q];
			tr[nq].len = tr[p].len + 1;
			tr[q].fa = tr[np].fa = nq;
			for(; p && tr[p].son[c] == q; p = tr[p].fa) tr[p].son[c] = nq;
		}
	}
}
int vis[N * 2];
void sol(int x, int l) {
	vis[x] = 1;
	for(int i = 25; i >= 0; i--) {
		if(!tr[x].son[i] || vis[tr[x].son[i]] )continue;
		sol(tr[x].son[i], l + 1);
	}
	if(ans[tr[x].pos ] == 0) {
		ans[tr[x].pos ] = tr[x].pos - l + 1 ;
	}
}
void solve() {
	cin >> s;
	for(auto c : s) {
		extend(c - 'a');
	}
	sol(1, 0);

	for(int i = 1; i <= (int)s.size(); i++ ) {
		cout << ans[i] << " " << i << " \n";
	}
	//cout << endl;
}
int  main() {
	IOS;
	int _ = 1;
	//cin>>_;
	while( _-- )
		solve();
}

/*
potato
1 1
1 2
3 3
3 4
3 5
5 6

*/





12 hdu 4436

  • 求本质不同的数字串的和 不考虑前导零 直接建立后缀自动机 然后做个dag上dp
  • num[ch[p][j]]+=num[p];
  • sum[ch[p][j]]=(sum[ch[p][j]]+(sum[p]*10+num[p]*j)%mod)%mod;

13 bzoj3413

  • 我们首先看后缀自动机是否有询问串 如果有那么失配次数 end[p]-len 否则为n
  • 然后我们考虑前缀的贡献 线段树合并去查询即可
  • 代码不给了 贴个链接link

14[AHOI2013]差异

  • 答案为 后缀上上任意两点的距离
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值