【阈值+SA+倍增+主席树】BZOJ5304 [HAOI2018] 字串覆盖

【题目】
lydsy
给定两个字符串 A , B A,B A,B以及一个参数 K K K,有 Q Q Q次询问每次给定 ( s , t , l , r ) (s,t,l,r) (s,t,l,r),令 T = A [ s … t ] , P = B [ l … r ] T=A[s\dots t],P=B[l\dots r] T=A[st],P=B[lr],每次若 T T T的一个子串与 P P P相同,就可以删掉 T T T的这个子串然后获得 K − i K-i Ki的收益,其中 i i i为初始 A A A中这个子串的起始位置。可以进行若干次操作,求最大收益。询问独立。

n , Q ≤ 1 0 5 , K ≤ 1 0 9 n,Q\leq 10^5,K\leq 10^9 n,Q105,K109,对于 n = 1 0 5 n=10^5 n=105的测试点, 51 ≤ r − l ≤ 2000 51\leq r-l\leq 2000 51rl2000的询问不超过 11000 11000 11000个且 r − l r-l rl在此区间内均匀随机。

【解题思路】
数据范围就奥妙重重。

首先对于每个询问,如果我们贪心地从左往右选择显然是正确的。那么接下来由于这个诡异的数据范围我们分类讨论一下。

如果 ∣ P ∣ |P| P比较大,那么它在区间内可匹配的位置不会很多(雾),只有 O ( n ∣ P ∣ ) O(\frac n {|P|}) O(Pn)个。那么我们只需要一个范围内最小可以匹配的位置然后暴力往后跳就行了。

于是我们可以先求出 A , B A,B A,B拼接后的后缀数组,能够匹配上 P P P的后缀起始位置在 s a sa sa上是一段连续的区间,考虑每次找出在 A A A [ l , r ] [l,r] [l,r]范围内合法的最小值,那么可以对后缀数组每个位置对应的 A A A上的位置建出主席树,每次就可以在主席树上二分找到第一个合法的位置。

单次查询的复杂度就是 O ( n ∣ P ∣ log ⁡ n ) O(\frac n {|P|} \log n) O(Pnlogn),再乘上一个询问次数 c n t cnt cnt(最多 1 0 4 10^4 104),由于这部分 ∣ P ∣ |P| P是均匀随机的,所以是可以接受的。

∣ P ∣ |P| P比较小,显然我们应该是要预处理答案的。对于每个长度进行预处理,那么每个合法串的起始位置都可以找到唯一一个最小的合法位置,满足以这两个位置往后匹配 l e n len len位相等且这两个串不交(当然也可能不存在)。这个东西构成了一个树形结构,我们构出这棵树后,对于一个询问再树上找到对应位置进行倍增计算即可。

这部分的复杂度就是 O ( 50 ⋅ n + Q log ⁡ n ) O(50\cdot n+Q\log n) O(50n+Qlogn)

【参考代码】

#include
using namespace std;

typedef long long ll;
const int N=2e5+10,M=N*30;
int fc[30],Log[N];

namespace IO
{
	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(ll x){if(x>9)write(x/10);putchar(x%10^48);}
	void writeln(ll x){write(x);putchar('\n');}
}
using namespace IO;

namespace SA
{
	int h[20][N],sa[N],rk[N],hi[N];
	int wa[N],wb[N],wx[N],wy[N];
	bool cmp(int *r,int a,int b,int l){return r[a]==r[b] && r[a+l]==r[b+l];}
	void getsa(int *r,int n,int m)
	{
		int *x=wa,*y=wb,*t,i,j,p;
		for(i=0;i<m;++i) wx[i]=0;
		for(i=0;i<n;++i) wx[x[i]=r[i]]++;
		for(i=1;i<m;++i) wx[i]+=wx[i-1];
		for(i=n-1;~i;--i) sa[--wx[x[i]]]=i;
		for(j=1,p=0;p<n;j<<=1,m=p)
		{
			for(p=0,i=n-j;i<n;++i) y[p++]=i;
			for(i=0;i<n;++i) if(sa[i]>=j) y[p++]=sa[i]-j;
			for(i=0;i<m;++i) wx[i]=0;
			for(i=0;i<n;++i) wx[wy[i]=x[y[i]]]++;
			for(i=1;i<m;++i) wx[i]+=wx[i-1];
			for(i=n-1;~i;--i) sa[--wx[wy[i]]]=y[i];
			for(t=x,x=y,y=t,p=1,i=1,x[sa[0]]=0;i<n;++i)
				x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++;
		}
	}
	void getheight(int *r,int n)
	{
		int i,j,k=0;
		for(i=1;i<=n;++i) rk[sa[i]]=i;
		for(i=0;i<n;hi[rk[i++]]=k)
			for(k?--k:0,j=sa[rk[i]-1];r[i+k]==r[j+k];++k);
	}
	void adjust(int n)
	{
		for(int i=n;i;--i) ++sa[i],rk[i]=rk[i-1];
		sa[0]=rk[0]=0;
	}
	void initrmq(int n)
	{
		for(int i=1;i<=n;++i) h[0][i]=hi[i];
		for(int j=1;j<18;++j) for(int i=1;i+fc[j]-1<=n;++i)
			h[j][i]=min(h[j-1][i],h[j-1][i+fc[j-1]]);
	}
	void initsa(int *r,int n,int m)
	{
		getsa(r,n+1,m);getheight(r,n);adjust(n);initrmq(n);
		// for(int i=0;i<=n;++i) printf("%d ",sa[i]); puts("");
		// for(int i=0;i<=n;++i) printf("%d ",rk[i]); puts("");
		// for(int i=0;i<=n;++i) printf("%d ",hi[i]); puts("");
	}
}
using namespace SA;

namespace Segment
{
	int rt[N];
	struct Seg
	{
		int sz,ls[M],rs[M],val[M];
		void copy(int x,int y){ls[x]=ls[y];rs[x]=rs[y];val[x]=val[y];}
		void update(int &x,int y,int l,int r,int p)
		{
			x=++sz;copy(x,y);val[x]++;
			if(l==r) return;
			int mid=(l+r)>>1;
			if(p<=mid) update(ls[x],ls[y],l,mid,p);
			else update(rs[x],rs[y],mid+1,r,p);
		}
		int query(int x,int y,int l,int r,int p)
		{
			if(!(val[y]-val[x])) return 0;
			if(l==r) return l;
			int mid=(l+r)>>1,res=0;
			if(p<=mid) res=query(ls[x],ls[y],l,mid,p);//wrong because if(p<=mid && val[ls[y]]-val[ls[x]]) return ql..
			if(!res) res=query(rs[x],rs[y],mid+1,r,p);
			return res;
		}
	}tr;
}
using namespace Segment;

namespace DreamLolita
{
	int n,K,Q;
	int a[N],pos[N],fa[18][N];
	char A[N];
	ll ans[N],sum[18][N];
	struct data
	{
		int s,t,l,len,p,id;
		data(int _s=0,int _t=0,int _l=0,int _e=0,int _p=0,int _i=0):s(_s),t(_t),l(_l),len(_e),p(_p),id(_i){}
		bool operator < (const data&rhs)const{return len==rhs.len?p<rhs.p:len<rhs.len;}
	}q[N];
	void initsth()
	{
		fc[0]=1;for(int i=1;i<=20;++i)fc[i]=fc[i-1]<<1;
		for(int i=2;i<N;++i) Log[i]=Log[i>>1]+1;
	}
	void solve1(int len,int &now)
	{
		for(int l=1,r=1;l<=(n<<1|1);l=r=r+1)
		{
			if(q[now].len^len) return;
			int cnt=0;
			while(h[0][r+1]>=len) ++r;
			for(int j=l;j<=r;++j) if(sa[j]<=n) pos[++cnt]=sa[j];
			if(q[now].p<l || q[now].p>r) continue;
			sort(pos+1,pos+cnt+1);pos[cnt+1]=n<<1|1;
			int k=1;
			for(int i=1;i<=cnt;++i)
			{
				while(pos[k]-pos[i]<len) ++k;
				fa[0][i]=k;sum[0][i]=K-pos[i];
			}
			int lim=Log[min(cnt+1,n/len)];
			for(int j=1;j<=lim;++j) for(int i=1;i<=cnt;++i)
				fa[j][i]=fa[j-1][fa[j-1][i]],sum[j][i]=sum[j-1][i]+sum[j-1][fa[j-1][i]];
			for(;q[now].len==len && l<=q[now].p && q[now].p<=r;++now)
			{
				int s=q[now].s,t=q[now].t,x=lower_bound(pos+1,pos+cnt+1,s)-pos;
				if(pos[x]<=t)
				{
					int y=x;ll res=0;
					for(int i=lim;~i;--i) if(pos[fa[i][y]] && pos[fa[i][y]]<=t)
						res+=sum[i][y],y=fa[i][y];
					ans[q[now].id]=res+sum[0][y];
				}
			}
			for(int j=0;j<=lim;++j) for(int i=1;i<=cnt;++i) 
				fa[j][i]=sum[j][i]=0;
		}
	}
	void solution()
	{
		initsth();
		n=read();K=read();
		scanf("%s",A);A[n]='z'+1;scanf("%s",A+n+1);
		for(int i=0;i<(n<<1|1);++i) a[i]=A[i]-'a'+1;
		initsa(a,n<<1|1,255);
		Q=read();
		for(int i=1;i<=Q;++i)
		{
			int s=read(),t=read(),l=read(),r=read();
			q[i]=data(s,t-(r-l),l,r-l+1,rk[n+l+1],i);
		}
		sort(q+1,q+Q+1);

		int p=1;
		for(int i=1;i<=50;++i) solve1(i,p);
		for(int i=1;i<=(n<<1|1);++i) 
			if(sa[i]<=n) tr.update(rt[i],rt[i-1],1,n,sa[i]);
			else rt[i]=rt[i-1];
		for(;p<=Q;++p)
		{
			int s=q[p].s,t=q[p].t,len=q[p].len,l=q[p].p,r=l;
			for(int i=Log[n]+1;~i;--i) 
				if(l-fc[i]>0 && h[i][l-fc[i]+1]>=len) l-=fc[i];
			for(int i=Log[n]+1;~i;--i)
				if(r+fc[i]<=(n<<1|1) && h[i][r+1]>=len) r+=fc[i];
			//printf("%d %d %d\n",l,r,len);
			for(int u=s,v;u<=n;u=v+len)
			{
				v=tr.query(rt[l-1],rt[r],1,n,u);
				//printf("%d %d %d %d\n",u,v,rt[l-1],rt[r]);
				if(v>t || !v) break;
				ans[q[p].id]+=K-v;
			}
		}
		for(int i=1;i<=Q;++i) writeln(ans[i]);
	}
}

int main()
{
#ifdef Durant_Lee
	freopen("BZOJ5304.in","r",stdin);
	freopen("BZOJ5304.out","w",stdout);
#endif
	DreamLolita::solution();
	return 0;
}

你可能感兴趣的:(数据结构-线段树,其他-贪心,字符串-后缀数组,其他-阈值)