[NOI2018]你的名字

38 篇文章 0 订阅
13 篇文章 0 订阅

题目

传送门 to luogu

思路

不难想到,把询问按照 r r r 排序。同样不难发现,这样没什么用。

用一种接地气的方法:先暴力,再优化虽然一般只有看过题解之后才知道这么推。

暴力:询问全串

先假设 l = 1 , r = n l=1,r=n l=1,r=n 。考虑对 S , T S,T S,T 都建立一个后缀自动机。为啥对 T T T 建立后缀自动机呢?因为我们要对 T T T子串去重。为啥对 S S S 建立后缀自动机呢?因为我们要 跑字符串匹配

我们试着用 S S S 的每个子串去筛掉不合法的 T T T 的子串。直接把 T T T S S S 上做字符串匹配,就会得到一个匹配长度 p i p_i pi ,意义是,在 T T T 长度为 i i i 的前缀上选一个后缀,长度必须大于 p i p_i pi 。然后,根据我们前面所说的,只这样搞有点问题。我们把 p i p_i pi 打到 T T T S A M \tt SAM SAM 上,取个 max ⁡ \max max

最后,我们重新扫一次 T T T 的后缀自动机,将这个值转移给父节点,然后计算答案。

变强:任意询问

想办法优化上面的这个做法。

复杂度瓶颈在哪里?在于每次要重新求一个 S [ l , r ] S[l,r] S[l,r] S A M \tt SAM SAM 。其目的是什么?求 p i p_i pi

如果我对全串 S S S 建自动机,还是能求出 p i p_i pi ,问题就已经解决了!

后缀自动机可持久化怎么样?搞点现实的。比如,根据一些信息忽略节点,使得没被忽略的节点与 S [ l , r ] S[l,r] S[l,r] 直接建立 S A M \tt SAM SAM 等效。

求出每个节点的 e n d p o s endpos endpos(字符串结束位置的集合)。那么,对于长度为 l e n len len 的字符串,只要 ∃ x ∈ e n d p o s ,    l + l e n − 1 ≤ x ≤ r \exist x\in endpos,\;l+len-1\le x\le r xendpos,l+len1xr 就行了!这个条件看上去挺好判断的。

不仅如此,每个节点的 l e n len len 也会改变。它本来的长度是 x x x ,但是因为有了出现位置的限制,它可能变成 max ⁡ y ∈ e n d p o s y ≤ r ( y − l + 1 ) \max_{y\in endpos}^{y\le r}(y-l+1) maxyendposyr(yl+1) 。两个值取较小即可。

最后一个问题:怎么求 e n d p o s endpos endpos 集合?当然啦,也可以不求出来,只要能进行上面两个判断就行。

不难发现,二者都只需要求出 e n d p o s endpos endpos 集合中不超过 r r r 的最大 x x x 。这东西可以用 权值线段树 来做。为什么会想到这种东西呢?因为它支持 可持久化合并。毕竟求 e n d p o s endpos endpos 这种东西,众所周知,是直接从子节点转移过来的。复杂度 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 就做完了。

另:对 r r r 排序后,用 L C T \tt LCT LCT 维护后缀树也可以方便的获得 e n d p o s endpos endpos有人写这种做法吗?

代码

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 2000005;
const int CharSiz = 26;

int n; // to let SgTree know right point
namespace SgTree{
	int son[MaxN*30][2], mx[MaxN*30];
	int totNode; // dynamic new node
	void pushUp(int o){
		if(son[o][1] == 0)
			mx[o] = mx[son[o][0]];
		else mx[o] = mx[son[o][1]];
	}
	int merge(int a,int b){
		if(!a || !b) return a+b;
		int x = ++ totNode; // lasting
		son[x][0] = merge(son[a][0],son[b][0]);
		son[x][1] = merge(son[a][1],son[b][1]);
		pushUp(x); return x;
	}
	int query(int qr,int o,int l=1,int r=n){
		if(!o) return 0; // empty set
		if(r <= qr) return mx[o]; // ql = 1
		int m = (l+r)>>1; // split line
		if(qr <= m) return query(qr,son[o][0],l,m);
		return max(query(qr,son[o][0],l,m),
			query(qr,son[o][1],m+1,r));
	}
	void modify(int qid,int &o,int l=1,int r=n){
		if(!o) o = ++ totNode; // must
		if(l == r) return void(mx[o] = l);
		int m = (l+r)>>1; // split line
		if(qid <= m) modify(qid,son[o][0],l,m);
		else modify(qid,son[o][1],m+1,r);
		pushUp(o); // dirrectly change it
	}
}

// helper of Bucket Sort
int item[MaxN], bucket[MaxN];

int rt[MaxN]; // for SgTree
struct SAM{
	int ch[MaxN][CharSiz];
	int fa[MaxN], len[MaxN];
	int cntNode, lst, ep[MaxN];
	SAM(){ cntNode = 0; }
	void clear(){
		memset(fa+1,0,cntNode<<2);
		for(int i=1; i<=cntNode; ++i)
			memset(ch[i],0,CharSiz<<2);
		memset(die+1,0,cntNode<<2);
		memset(ep+1,0,cntNode<<2);
		cntNode = lst = 1;
	}
	void add(char c){
		int p = lst; lst = ++ cntNode;
		int n = lst; len[n] = len[p]+1;
		ep[n] = len[n]; // endpos
		for(; p&&!ch[p][c]; p=fa[p])
			ch[p][c] = n; // link
		if(!p) return void(fa[n] = 1);
		int q = ch[p][c]; // maybe
		if(len[q] == len[p]+1)
			return void(fa[n] = q);
		int nq = ++ cntNode;
		memcpy(ch[nq],ch[q],CharSiz<<2);
		fa[nq] = fa[q]; // copy
		len[nq] = len[p]+1;
		fa[n] = fa[q] = nq;
		for(; p&&ch[p][c]==q; p=fa[p])
			ch[p][c] = nq;
	}
	void Sort(){
		memset(bucket+1,0,cntNode<<2);
		for(int i=2; i<=cntNode; ++i)
			++ bucket[len[i]]; // except 1
		for(int i=2; i<=cntNode; ++i)
			bucket[i] += bucket[i-1];
		for(int i=2; i<=cntNode; ++i)
			item[bucket[len[i]]--] = i;
	}
	/** @brief build SgTree */
	void build(){
		Sort(); // topo
		for(int i=cntNode-1; i; --i){
			int x = item[i]; // processing
			if(ep[x]) // a prefix
				SgTree::modify(ep[x],rt[x]);
			rt[fa[x]] = SgTree::merge(rt[fa[x]],rt[x]);
		}
	}
	int die[MaxN]; // tag of "p_i"
	long long calc(){
		Sort(); long long res = 0;
		for(int i=cntNode-1; i; --i){
			int x = item[i]; // processing
			die[x] = min(die[x],len[x]);
			res += len[x]-max(len[fa[x]],die[x]);
			die[fa[x]] = max(die[fa[x]],die[x]);
		}
		return res;
	}
};
SAM S, T; // two SAMs
char str[MaxN]; // original str(T)
void match(int len,int L,int R){
	T.clear(); // many asks
	int s = 1, t = 1; // node on SAM
	int lens = 0; // how long do we match
	for(int i=0; i<len; ++i){
		char c = str[i]-'a';
		T.add(c); // construct meanwhile
		for(; s; s=S.fa[s],lens=S.len[s]){
			int nxt = S.ch[s][c];
			if(!nxt) continue;
// printf("nxt = %d (len = %d, ep = %d)\n",nxt,S.len[nxt],S.ep[nxt]);
			int ep = SgTree::query(R,rt[nxt]);
// printf("ep = %d\n",ep);
			ep = ep-L+1; // longest
			if(S.len[S.fa[nxt]]+1 <= ep){
				lens = min(lens+1,ep);
				s = nxt; break;
			}
		}
		if(!s){ s = 1; lens = 0; }
// printf("lens = %d\n",lens);
		for(; t&&!T.ch[t][c]; t=T.fa[t]);
		for(t=T.ch[t][c]; t; t=T.fa[t])
			if(T.len[T.fa[t]]+1 <= lens)
				break; // just stop here
		if(!t) t = 1; // empty
		T.die[t] = max(T.die[t],lens);
	}
	printf("%lld\n",T.calc());
}

int main(){
	S.clear(); // or "cntNode" is bad
	scanf("%s",str), n = strlen(str);
	for(int i=0; i<n; ++i)
		S.add(str[i]-'a');
	S.build(); int q = readint();
	for(int L,R; q; --q){
		scanf("%s",str);
		L = readint(), R = readint();
		match(strlen(str),L,R);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值