【SAM+扫描线+BIT】CC_SUBQUERY Substring Query

【题目】
Codechef
给定一个字符串 S S S Q Q Q个询问,每个询问形如 ( l i , p i ) (l_i,p_i) (li,pi),回答有多少个长度为 l i l_i li的字符串在 S S S中恰好出现了 p i p_i pi次。
∣ S ∣ ≤ 2 × 1 0 5 , Q ≤ 5 × 1 0 5 |S|\leq 2\times 10^5,Q\leq 5\times 10^5 S2×105,Q5×105

【解题思路】
字符串题,不妨先建出 SAM \text{SAM} SAM

我们知道 SAM \text{SAM} SAM上的每个节点代表了长度为一段区间的字符串,而 right \text{right} right集合的大小即这些字符串的出现次数。

于是我们不妨在一个二维平面上表示出来,纵坐标代表字符串长度,横坐标代表出现次数,那么每个节点就对应二维平面上一条垂直 x x x轴的线段。对于每个询问,即求一个点 ( p i , l i ) (p_i,l_i) (pi,li)被多少条线段覆盖过。

这就是一个经典的扫描线问题。(而且只是线段就更舒服了,值域又这么小,直接打个标记 BIT \text{BIT} BIT维护就行了)

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

【参考代码】

#include<bits/stdc++.h>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;

typedef pair<int,int> pii;
const int N=5e5+10;

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}
void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
void writeln(int x){write(x);putchar('\n');}

namespace SAM
{
	int b[N],c[N];
	struct SAM
	{
		int sz,las,ch[N][26],fa[N],siz[N],mx[N];
		void init(){las=sz=1;}
		void extend(int x)
		{
			int p,q,np,nq;
			p=las;las=np=++sz;mx[np]=mx[p]+1;++siz[las];
			for(;p && !ch[p][x];p=fa[p]) ch[p][x]=np;
			if(!p) fa[np]=1;
			else 
			{
				q=ch[p][x];
				if(mx[q]==mx[p]+1) fa[np]=q;
				else
				{
					nq=++sz;mx[nq]=mx[p]+1;
					memcpy(ch[nq],ch[q],sizeof(ch[q]));
					fa[nq]=fa[q];fa[q]=fa[np]=nq;
					for(;p && ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
				}
			}
		}
		void merge()
		{
			for(int i=1;i<=sz;++i) b[mx[i]]++;
			for(int i=1;i<=sz;++i) b[i]+=b[i-1];
			for(int i=sz;i;--i) c[b[mx[i]]--]=i;
			for(int i=sz,x;i;--i) x=c[i],siz[fa[x]]+=siz[x]; 
			//for(int i=2;i<=sz;++i) printf("%d %d %d %d %d\n",i,fa[i],siz[i],mx[i],mx[fa[i]]);
		}
	}S;
}
using  namespace SAM;

namespace DreamLolita
{
	int n,Q;
	int ans[N];
	char s[N];
	vector<pii>vec[N],qr[N];

	struct BIT
	{
		#define lowbit(x) (x&(-x))
		int c[N];
		void up(int x,int v){for(;x<N;x+=lowbit(x))c[x]+=v;}
		void update(int x,int y,int v){up(x,v);up(y+1,-v);}
		int query(int x){int res=0;for(;x;x-=lowbit(x))res+=c[x];return res;}
		#undef lowbit
	}bit;

	void solve()
	{
		scanf("%s",s+1);n=strlen(s+1);S.init();
		for(int i=1;i<=n;++i) S.extend(s[i]-'a'); 
		S.merge();
		for(int i=2;i<=S.sz;++i) vec[S.siz[i]].pb(mkp(S.mx[S.fa[i]]+1,S.mx[i]));

		Q=read();
		for(int i=1,l,p;i<=Q;++i) l=read(),p=read(),qr[p].pb(mkp(l,i));

		for(int i=1;i<=n;++i)
		{
			if(!qr[i].size() || !vec[i].size()) continue;
			for(auto j:vec[i]) bit.update(j.fi,j.se,1);
			for(auto j:qr[i]) ans[j.se]=bit.query(j.fi);
			for(auto j:vec[i]) bit.update(j.fi,j.se,-1);
		} 
		for(int i=1;i<=Q;++i) writeln(ans[i]);
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("CC_SUBQUERY.in","r",stdin);
	freopen("CC_SUBQUERY.out","w",stdout);
#endif
	DreamLolita::solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值