P4770 [NOI2018]你的名字 [SAM+主席树]

传送门

渐渐发现, 自己曾经认为的不可做题, 被一道一道地做了

尽管机房盛行装菜, 但无论是我, 还是机房的伙伴, 都在一天一天地变强

A了这道题, 可能也只有感动, 激动, 自豪能形容我的心情


解题思路 : 

先考虑暴力

我最原始的暴力 ---- 

把 l -- r 之间的拿出来建SAM, 然后询问串的每一个后缀那上去跑, 终止位置记为 pos

那么 i -- pos 这些都是出现了的, 对答案的贡献就是 n - pos

考虑优化一下, 我们能不能把询问串拿上去只跑一次呢 ? 当然可以

我们记 len[i] 为 [1--i] 的后缀, 能在原串匹配的最长长度, 显然对答案的贡献就是 i - len[i]

怎么跑呢?

先考虑 l = 1, r = |S|的情况

如果下一位有这个字母, 那么len[i]++, 并且走到那个字母, 否则跳后缀链接, 并将 len[i] 更新

那么其他情况呢 ? 我开始想偏了, 想什么离线啊之类的 ...

其实往简单的想, 如果那个点的 endpos 集合一个在 L -- R 之间的都没有, 我们不走就是了

怎么记录 endpos 集合 ? 我们发现一个点的 endpos 集合就是它的子树的所有点的 pos

那么我们对 parent 树按dfs序建一个主席树, 就可以查询了

已经打算开始写了,  却又暴露了我思维不严谨的地方

询问串的一个子串可能出现多次, 难道每次都要算到答案里吗?

我们需要去重 ! 那么对于询问串再建一个SAM, 对于SAM上的每一个节点来考虑

还有一些细节, 比如说不是查 [L, R] 区间, 而要查[L + 当前以经匹配的len, R] 的区间

然后失配过后不能直接跳 link, 因为我们将 len 缩小过后有可能要查的串就出现了

思维不严谨啊 !


#include<bits/stdc++.h>
#define N 2000050
using namespace std;
typedef long long ll;
char S[N];
int T, n, c[N], A[N], p[N];
char s[N];
vector<int> v[N]; int st[N], ed[N], sign, rt[N];
struct Segmentree{
	int ls[N * 15], rs[N * 15], sum[N * 15], tot;
	#define mid ((l+r) >> 1)
	void Build(int &x, int l, int r){
		x = ++tot; if(l == r) return; 
		Build(ls[x], l, mid); Build(rs[x], mid+1, r);
	}
	void Insert(int &x, int last, int l, int r, int pos){
		x = ++tot; ls[x] = ls[last]; rs[x] = rs[last]; sum[x] = sum[last] + 1;
		if(l == r) return; 
		if(pos <= mid) Insert(ls[x], ls[last], l, mid, pos);
		else Insert(rs[x], rs[last], mid+1, r, pos);
	}
	int Quary(int a, int b, int l, int r, int L, int R){
		if(!a) return 0;
		if(L<=l && r<=R) return sum[a] - sum[b];
		int ans = 0;
		if(L<=mid) ans += Quary(ls[a], ls[b], l, mid, L, R);
		if(R>mid) ans += Quary(rs[a], rs[b], mid+1, r, L, R);
		return ans;
	}
}Seg;
struct SAM{
	struct Node{ int ch[26], link, len, pos;} t[N];
	int tot, last;
	SAM(){ tot = last = 1;}
	void clear(){ 
		for(int i=1; i<=tot; i++){
			for(int j=0; j<26; j++) t[i].ch[j] = 0;
			t[i].link = t[i].len = t[i].pos = 0;
		} tot = last = 1;
	}
	void Extend(int Id, int c){
		int cur = ++tot, p = last;
		t[cur].len = t[p].len + 1; t[cur].pos = Id;
		for(;p && !t[p].ch[c]; p = t[p].link) t[p].ch[c] = cur;
		if(!p) t[cur].link = 1;
		else{
			int q = t[p].ch[c];
			if(t[q].len == t[p].len + 1) t[cur].link = q;
			else{
				int clone = ++tot; t[clone].pos = t[q].pos;
				t[clone].link = t[q].link; t[clone].len = t[p].len + 1;
				for(int i=0; i<26; i++) t[clone].ch[i] = t[q].ch[i];
				for(;p && t[p].ch[c] == q; p = t[p].link) t[p].ch[c] = clone;
				t[q].link = t[cur].link = clone;
			}
		} last = cur;
	} 
	void dfs(int u){
		st[u] = ++sign; rt[sign] = rt[sign - 1];
		if(t[u].pos) Seg.Insert(rt[sign], rt[sign], 1, n, t[u].pos);
		for(int i=0; i<v[u].size(); i++){
			int t = v[u][i]; dfs(t); 
		} ed[u] = sign;
	}
	void Build(){
		for(int i=2; i<=tot; i++) v[t[i].link].push_back(i);
		Seg.Build(rt[0], 1, n); dfs(1);
	} 
	void Find(int &u, int &len, int L, int R, int c){
		while(1){
			int son = t[u].ch[c];
			if(t[u].ch[c] && Seg.Quary(rt[ed[son]], rt[st[son]-1], 1, n, L+len, R)){
					len++; u = son; return;
			}
			if(!len) return; 
			len--; if(len < t[t[u].link].len + 1) u = t[u].link;
		}
	}
	ll calc(){
		ll ans = 0;
		for(int i=2; i<=tot; i++){
			ans += max(0, t[i].len - max(t[t[i].link].len, p[t[i].pos]));
		} return ans;
	}
}sam1, sam2;
int main(){
	scanf("%s", S+1); n = strlen(S+1);
	for(int i=1; i<=n; i++) sam1.Extend(i, S[i] - 'a');
	sam1.Build();
	scanf("%d", &T);
	while(T--){
		scanf("%s", s+1); 
		int len = strlen(s+1);
		int l, r; scanf("%d%d", &l, &r);
		sam2.clear();
		int now = 1;
		for(int i=1; i<=len; i++){
			p[i] = p[i-1]; 
			sam1.Find(now, p[i], l, r, s[i] - 'a');
		}
		for(int i=1; i<=len; i++) sam2.Extend(i, s[i] - 'a');
		printf("%lld\n", sam2.calc());
	} return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值