【SCOI2012】喵星球上的点名(SAM)(树上差分)

传送门


原题数据过水导致一个log跑不过根号

目前知道两种根号做法,暴跳SAM的根号做法和本身就是根号的莫队做法,如果要卡的话这两种是绝对不可能比log快的。来加一个强制在线

题解:

首先用map存转移把广义SAM建出来。

我们注意到第一个询问就是问终点的fail树内部有多少个不同的串。

直接树上差分打标记解决。

第二个询问就是问它的所有匹配点在fail树上到根的链的并集上有多少个之前的询问的终点。

还是树上差分。。。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++; 
	}
	
	template<typename T>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num; 
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

namespace Tree{
	cs int N=4e5+7;
	std::vector<int> G[N];
	
	int fa[N],son[N],d[N]; 
	int siz[N],top[N],in[N],dfn;
	
	void dfs1(int u,int p){
		siz[u]=1;fa[u]=p,d[u]=d[p]+1;
		for(int re v:G[u]){
			dfs1(v,u);siz[u]+=siz[v];
			if(siz[v]>siz[son[u]])son[u]=v;
		}
	}
	void dfs2(int u,int tp){
		in[u]=++dfn;top[u]=tp;
		for(int re v:G[u])
		dfs2(v,v==son[u]?tp:v);
	}
	inline int LCA(int u,int v){
		while(top[u]!=top[v])d[top[u]]<d[top[v]]?v=fa[top[v]]:u=fa[top[u]];
		return d[u]<d[v]?u:v;
	}
	inline void init(){
		dfs1(1,0);
		dfs2(1,1);
	}
}

namespace SAM{
	cs int N=4e5+7;
	std::map<int,int> son[N];
	int fa[N],len[N],now=1;
	inline int push_back(int p,int c){
		auto it=son[p].find(c);
		if(it!=son[p].end()){
			if(len[it->second]==len[p]+1)return it->second;
			else {
				int nq=++now,q=it->second;
				son[nq]=son[q],len[nq]=len[p]+1;
				fa[nq]=fa[q],fa[q]=nq;
				for(;p&&(it=son[p].find(c),it->second==q);p=fa[p])it->second=nq;
				return nq;
			}
		}
		else {
			int cur=++now;len[cur]=len[p]+1;
			for(;p&&!son[p].count(c);p=fa[p])son[p][c]=cur;
			if(!p)fa[cur]=1;
			else {
				it=son[p].find(c);
				if(len[it->second]==len[p]+1)fa[cur]=it->second;
				else {
					int nq=++now,q=it->second;
					son[nq]=son[q],len[nq]=len[p]+1;
					fa[nq]=fa[q],fa[cur]=fa[q]=nq;
					for(;p&&(it=son[p].find(c),it->second==q);p=fa[p])it->second=nq;
				}
			}
			return cur;
		}
	}
	int bin[N],nd[N];
	inline void build(){
		for(int re i=1;i<=now;++i)++bin[len[i]];
		for(int re i=1;i<=now;++i)bin[i]+=bin[i-1];
		for(int re i=1;i<=now;++i)nd[bin[len[i]]--]=i;
		for(int re i=2;i<=now;++i)Tree::G[fa[i]].push_back(i);
	}
	int tag[N],val[N];
	inline void push_up(){
		for(int re i=now;i;--i){
			int u=nd[i];
			val[u]+=tag[u],tag[u]=0;
			val[fa[u]]+=val[u];
		}
	}
	inline void push_down(){
		val[1]=tag[1],tag[1]=0;val[0]=0;
		for(int re i=2;i<=now;++i){
			int u=nd[i];
			val[u]=val[fa[u]]+tag[u];
			tag[u]=0;
		}
	}
	inline void query(){
		int l=gi(),u=1,flag=true;
		while(l--){
			int c=gi();
			if(flag){
				auto it=son[u].find(c);
				if(it==son[u].end())flag=false;
				else u=it->second;
			}
		}
		if(flag)++tag[u],cout<<val[u]<<"\n";
		else cout<<0<<"\n";
	}
}

cs int N=5e4+7,M=1e5+7;

int n,m;

int l1[N],l2[N];
int s[M<<1|1],ps[M<<1|1],lca[M<<1|1];

signed main(){
#ifdef zxyoi
	freopen("meow.in","r",stdin);
#endif
	n=gi(),m=gi();
	for(int re i=1;i<=n;++i){
		l1[i]=gi()+l2[i-1];
		for(int re j=l2[i-1]+1,p=1;j<=l1[i];++j)
		ps[j]=p=SAM::push_back(p,s[j]=gi());
		l2[i]=gi()+l1[i];
		for(int re j=l1[i]+1,p=1;j<=l2[i];++j)
		ps[j]=p=SAM::push_back(p,s[j]=gi());
	}
	SAM::build();Tree::init();
	for(int re i=1;i<=n;++i){
		std::sort(ps+l2[i-1]+1,ps+l2[i]+1,[](int u,int v){
			return Tree::in[u]<Tree::in[v];
		});
	}
	for(int re i=1;i<=n;++i){
		for(int re j=l2[i-1]+2;j<=l2[i];++j)
		lca[j]=Tree::LCA(ps[j-1],ps[j]);
	}
	for(int re i=1;i<=l2[n];++i)SAM::tag[ps[i]]++,SAM::tag[lca[i]]--;
	SAM::push_up();while(m--)SAM::query();SAM::push_down();
	for(int re i=1;i<=n;++i){
		int ans=0;
		for(int re j=l2[i-1]+1;j<=l2[i];++j)
		ans+=SAM::val[ps[j]]-SAM::val[lca[j]];
		cout<<ans<<" ";
	}
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值