区间本质不同子串个数

一、题目

点此看题

二、解法

这道题需要离线,我们一个一个加入字符,然后回答右端点相应的询问。那对于不同的左端点怎么办呢?可以暴力一点:尝试 维护出所有左端点的答案,然后直接暴力拿就行了。

首先对于一个本质不同的子串,设他的最后出现位置是 \(pos\)(指的是 \(\tt endpos\) ),那么对于左端点 \(l\in[1,pos-|T|+1]\) 就可以获得 \(1\) 的贡献。而根据后缀自动机的性质,每次只会是 \(last\) 到根的链上会有修改,而他们的 \(pos\) 都会变成现在的 \(i\)

要搞出一个能维护链的数据结构,你仔细想一下就发现树链剖分也没有什么用。我们选择 \(\tt link-cut-tree\)

但要对它有一个更深的理解,因为我们的修改本质上是 后缀树上到根链的修改套上线段树上的修改 (因为要维护所有左端点的答案,实现中线段树维护的是差分标记),我们唯一可以加速的就是把 \(pos\) 相同且紧挨在一起的点一起修改了(因为子串长度是连续的)

又要保证复杂度,所以我们把他和 \(\tt link-cut-tree\)\(\tt splay\) 的均摊分析联系在一起,我们在 \(access\) 操作的时候,把 \(x\) 转到根以后,直接把他管辖的这一小段一并改了(具体写法就是改长度 \((len[par[x]],len[x]]\)\(pos\)),因为 这一小段原来是实边相连的,他们的 \(pos\) 一定是一样的

\(access\) 的基本复杂度是 \(O(n\log n)\),套上他之后就变成了 \(O(n\log^2 n)\),写起来并不麻烦。

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 200005;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
//suffix tree 
int n,m,cnt,last,p[M];char s[M];
vector<pair<int,int> > q[M];ll ans[M];
struct node
{
	int fa,len,ch[26];
}a[M];
void add(int c)
{
    int p=last,np=last=++cnt;
    a[np].len=a[p].len+1;
    for(;p && !a[p].ch[c];p=a[p].fa) a[p].ch[c]=np;
    if(!p) a[np].fa=1;
    else
    {
        int q=a[p].ch[c];
        if(a[q].len==a[p].len+1) a[np].fa=q;
        else
        {
            int nq=++cnt;
            a[nq]=a[q];a[nq].len=a[p].len+1;
            a[q].fa=a[np].fa=nq;
            for(;p && a[p].ch[c]==q;p=a[p].fa) a[p].ch[c]=nq;
        }
    }
}
//segment tree
struct jzm
{
	ll sum[2*M],tag[2*M];
	void up(int i)
	{
		sum[i]=sum[i<<1]+sum[i<<1|1]; 
	}
	void down(int i,int l,int r)
	{
		int mid=(l+r)>>1;
		if(tag[i])
		{
			tag[i<<1]+=tag[i];
			tag[i<<1|1]+=tag[i];
			sum[i<<1]+=1ll*tag[i]*(mid-l+1);
			sum[i<<1|1]+=1ll*tag[i]*(r-mid);
			tag[i]=0;
		}
	}
	void upd(int i,int l,int r,int L,int R,int x)
	{
		if(L>r || l>R) return ;
		if(L<=l && r<=R)
		{
			sum[i]+=1ll*(r-l+1)*x;
			tag[i]+=x;
			return ;
		}
		int mid=(l+r)>>1;
		down(i,l,r);
		upd(i<<1,l,mid,L,R,x);
		upd(i<<1|1,mid+1,r,L,R,x);
		up(i);
	}
	ll ask(int i,int l,int r,int L,int R)
	{
		if(l>R || L>r) return 0;
		if(L<=l && r<=R) return sum[i];
		int mid=(l+r)>>1;
		down(i,l,r);
		return ask(i<<1,l,mid,L,R)+ask(i<<1|1,mid+1,r,L,R);
	}
}T;
//link-cut-tree
int par[M],ch[M][2],val[M],tag[M],st[M];
int nrt(int x)
{
	return ch[par[x]][0]==x || ch[par[x]][1]==x;
}
int chk(int x)
{
	return ch[par[x]][1]==x;
}
void down(int x)
{
	if(x && tag[x])
	{
		if(ch[x][0]) val[ch[x][0]]=tag[ch[x][0]]=tag[x];
		if(ch[x][1]) val[ch[x][1]]=tag[ch[x][1]]=tag[x];
		tag[x]=0;
	}
}
void rotate(int x)
{
	int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
	ch[y][k]=w;par[w]=y;
	if(nrt(y)) ch[z][chk(y)]=x;par[x]=z;
	ch[x][k^1]=y;par[y]=x;
}
void splay(int x)
{
	int y=x,z=0;
	st[++z]=y;
	while(nrt(y)) st[++z]=y=par[y];
	while(z) down(st[z--]);
	while(nrt(x))
	{
		int y=par[x],z=par[y];
		if(nrt(y))
		{
			if(chk(y)==chk(x)) rotate(y);
			else rotate(x);
		}
		rotate(x);
	}
}
void access(int x,int k)
{
	int y=0;
	for(;x;x=par[y=x])
	{
		splay(x);ch[x][1]=y;
		if(val[x]) T.upd(1,1,n,val[x]-a[x].len+1,val[x]-a[par[x]].len,-1);
	}
	val[y]=tag[y]=k;T.upd(1,1,n,1,k,1);
}
signed main()
{
	scanf("%s",s+1),n=strlen(s+1);m=read();
	last=cnt=1;
	for(int i=1;i<=n;i++)
	{
		add(s[i]-'a');
		p[i]=last;
	}
	for(int i=2;i<=cnt;i++) par[i]=a[i].fa;
	for(int i=1;i<=m;i++)
	{
		int l=read(),r=read();
		q[r].push_back(make_pair(l,i));
	}
	for(int i=1;i<=n;i++)
	{
		access(p[i],i);
		for(int j=0;j<q[i].size();j++)//nmsl,这里打成i++了... 
		{
			int l=q[i][j].first,id=q[i][j].second;
			ans[id]=T.ask(1,1,n,l,i);
		}
	}
	for(int i=1;i<=m;i++)
		printf("%lld\n",ans[i]);
}
posted @ 2020-12-20 14:52  C202044zxy  阅读(581)  评论(1编辑  收藏  举报