洛谷 P5310 - 遥远的过去

人生第一道Ynoi祭!

听fz说这是上午讲的题?

洛谷题目页面传送门

给定两个内部元素两两不同的序列\(a,b,|a|=n,|b|=m\)。定义两个内部元素两两不同的序列本质相同当且仅当它们各自离散化后相同。支持\(q\)次操作,每次操作修改\(b\)的一次元素(保证修改完依然内部元素两两不同),要求回答\(b\)\(a\)中有多少次匹配。

\(n,m,q\in\left[1,10^5\right]\)

首先带修的话其他字符串匹配算法基本上用不起来了,想到万能的哈希。

考虑先预处理出\(a\)中所有长度为\(m\)的子串的哈希值,存到一个map里,然后操作的时候就实时维护\(b\)的哈希值,往map里一查即可。

考虑最常规的滚动哈希\(hsh(x)=\sum\limits_{i=1}^{|x|}b^{|x|-i}x_i\bmod p\),其中\(b\)是哈希base,\(p\)是哈希模数。为了方便,这里不考虑取模运算。

总结一下有哪些操作:

  1. 删除开头元素,并实时更新\(x\)(这里的\(x\)是排名);
  2. 在末尾添加元素,并实时更新\(x\),实时更新\(|x|-i\)(其实就是全体加一);
  3. 修改元素,并实时更新\(x\)
  4. 查询哈希值。

注意到要对排名操作,不难想到基于值域建一个平衡树(这里使用fhq-Treap),支持插入和删除和查哈希值。那么,对于每个节点,键值是\(x_i\),我们还需要维护对应的\(|x|-i\),全体加一再简单不过了,懒标记即可。还要维护哈希值。

不难发现,此时是排名连续形成区间,而不是下标连续形成区间。一种维护策略是:对于每个节点,维护以它为根的子树形成的排名区间组成的序列中,若认为它们的排名是从\(1\)开始算的(下标以维护的为准),这样得到的哈希值。合并两个序列(其中第二个序列的任意元素大于第一个序列的任意元素)就很简单了,本来认为第二个序列的排名是从\(1\)开始的,现在发现是从第一个序列的长度加一开始的(第一个序列不变),根据哈希函数可以得到第二个序列每个元素对应的贡献都要加上\(b^{|x|-i}\)乘以第一个序列的长度,那么总共先就是\(\sum b^{|x|-i}\)乘以第一个序列的长度。于是我们还需要维护每个节点的\(\sum b^{|x|-i}\),这个合并的话就简单加起来即可。

预处理\(b\)的幂,时间复杂度\(\mathrm O(n\log n)\)(认为\(n,m,q\)同阶)。平衡树的删除使用了垃圾回收。使用了双模,跑得还挺快(不开O2)。

代码:

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
mt19937 rng(20060617);
const int N=100000,M=100000;
int n,m,qu;
int a[N+1],b[M+1];
const int hbase1=131,hmod1=998244353,hbase2=13331,hmod2=1000000007;//哈希参数 
int pw1[N+1],pw2[N+1];
struct fhq_treap{//平衡树 
	int sz,root;
	struct node{unsigned key;int lson,rson,sz,v1,v2,sum1,hsh1,sum2,hsh2,lz;}nd[M+1];
	#define key(p) nd[p].key
	#define lson(p) nd[p].lson
	#define rson(p) nd[p].rson
	#define sz(p) nd[p].sz
	#define v1(p) nd[p].v1
	#define v2(p) nd[p].v2
	#define sum1(p) nd[p].sum1
	#define hsh1(p) nd[p].hsh1
	#define sum2(p) nd[p].sum2
	#define hsh2(p) nd[p].hsh2
	#define lz(p) nd[p].lz
	stack<int> bin;
	void init(){//初始化 
		sz=root=0;
		nd[0]=node({0,0,0,0,0,0,0,0,0,0,0});
		while(bin.size())bin.pop();
	}
	int nwnd(int v1,int v2){//新建节点 
		int p;
		if(bin.size())p=bin.top(),bin.pop();
		else p=++sz;
		return nd[p]=node({rng(),0,0,1,v1,v2,pw1[v2],pw1[v2],pw2[v2],pw2[v2],0}),p;
	}
	void recyc(int p){bin.push(p);}//垃圾回收 
	void sprup(int p){//上传 
		sz(p)=sz(lson(p))+1+sz(rson(p));
		sum1(p)=(1ll*sum1(lson(p))+pw1[v2(p)]+sum1(rson(p)))%hmod1;
		hsh1(p)=(hsh1(lson(p))+1ll*(sz(lson(p))+1)*pw1[v2(p)]+hsh1(rson(p))+1ll*(sz(lson(p))+1)*sum1(rson(p)))%hmod1;
		sum2(p)=(1ll*sum2(lson(p))+pw2[v2(p)]+sum2(rson(p)))%hmod2;
		hsh2(p)=(hsh2(lson(p))+1ll*(sz(lson(p))+1)*pw2[v2(p)]+hsh2(rson(p))+1ll*(sz(lson(p))+1)*sum2(rson(p)))%hmod2;
	}
	void tag(int p,int v=1){//打懒标记并修改 
		v2(p)+=v;lz(p)+=v;
		sum1(p)=1ll*sum1(p)*pw1[v]%hmod1;hsh1(p)=1ll*hsh1(p)*pw1[v]%hmod1;
		sum2(p)=1ll*sum2(p)*pw2[v]%hmod2;hsh2(p)=1ll*hsh2(p)*pw2[v]%hmod2;
	}
	void sprdwn(int p){//下传懒标记 
		if(lz(p)){
			tag(lson(p),lz(p));tag(rson(p),lz(p));
			lz(p)=0;
		}
	}
	pair<int,int> split(int x,int p=-1){~p||(p=root);
		if(!x)return mp(0,p);
		sprdwn(p);
		pair<int,int> sp;
		if(x<=sz(lson(p)))return sp=split(x,lson(p)),lson(p)=sp.Y,sprup(p),mp(sp.X,p);
		return sp=split(x-1-sz(lson(p)),rson(p)),rson(p)=sp.X,sprup(p),mp(p,sp.Y);
	}
	int mrg(int p,int q){
		if(!p||!q)return p|q;
		sprdwn(p);sprdwn(q);
		if(key(p)<key(q))return rson(p)=mrg(rson(p),q),sprup(p),p;
		return lson(q)=mrg(p,lson(q)),sprup(q),q;
	}
	int lss(int v,int p=-1){~p||(p=root);
		if(!p)return 0;
		sprdwn(p);
		if(v1(p)<v)return sz(lson(p))+1+lss(v,rson(p));
		return lss(v,lson(p));
	}
	void insert(int v1,int v2){//插入 
		pair<int,int> sp=split(lss(v1));
		root=mrg(mrg(sp.X,nwnd(v1,v2)),sp.Y);
	}
	void del(int v){//删除 
		pair<int,int> sp1=split(lss(v)),sp2=split(1,sp1.Y);
		recyc(sp2.X);
		root=mrg(sp1.X,sp2.Y);
	}
	void add(){tag(root);}//全体加一 
	pair<int,int> hsh(){return mp(hsh1(root),hsh2(root));}//查哈希值 
}trp;
map<pair<int,int>,int> hav;//桶 
int main(){
	cin>>n>>m>>qu;
	for(int i=1;i<=n;i++)scanf("%d",a+i);
	for(int i=1;i<=m;i++)scanf("%d",b+i);
	pw1[0]=pw2[0]=1;
	for(int i=1;i<=max(n,m);i++)pw1[i]=1ll*pw1[i-1]*hbase1%hmod1,pw2[i]=1ll*pw2[i-1]*hbase2%hmod2;//预处理幂 
	if(m<=n){//预处理 
		trp.init();
		for(int i=1;i<=m;i++)trp.insert(a[i],m-i);
		hav[trp.hsh()]++;
//		printf("(%d,%d)\n",trp.hsh().X,trp.hsh().Y);
		for(int i=m+1;i<=n;i++){
			trp.del(a[i-m]);trp.add();trp.insert(a[i],0);//变成下一个子串 
			hav[trp.hsh()]++;
//			printf("(%d,%d)\n",trp.hsh().X,trp.hsh().Y);
		}
	}
	trp.init();
	for(int i=1;i<=m;i++)trp.insert(b[i],m-i);
//	printf("\n(%d,%d)\n",trp.hsh().X,trp.hsh().Y);
	while(qu--){
		int x,y;
		scanf("%d%d",&x,&y);
		trp.del(b[x]);trp.insert(b[x]=y,m-x);//修改 
//		printf("(%d,%d)\n",trp.hsh().X,trp.hsh().Y);
		printf("%d\n",hav[trp.hsh()]);
	}
	return 0;
}
posted @ 2020-08-03 17:04  ycx060617  阅读(135)  评论(2编辑  收藏  举报