Meaning
给定一些数字,对它们进行首尾相接和断开两种操作。对于每次询问,求对于每个数字,其后长度一定的数字串在给定数字串中出现的次数,并给出这些次数之积。
Soultion
对于每次首尾相接或断开的操作,如果直接对断点或合点两侧的整个数字串进行操作,时间复杂度不可接受。由于每次查询只有向后 \(k\) 数字串会产生贡献,所以假设 \(k\) 一定,则只需要对断点或合点左侧 \(k-1\) 个数字进行操作。这道题的向后 \(k\) 数字串的长度极为有限,所以当断点或合点左侧最靠近断点或合点的数字是需要修改的数字的第 \(i\) 个后继时,我们即使在每次操作时对每一个需要修改的数字修改它的向后 \(i+2\) 到 \(50\)( \(k\) 的上界)数字串,也不会产生过大的时间开销。
在具体的操作过程中,我们可以取出以断点或合点为圆心,以 \(50\) 为半径的圆内的点,对断点或合点左侧的数字枚举向后数字串的长度记录这个数字串并累加它的出现次数即可。
为了方便取出这些圆上的点,我们可以使用链表对每个数字的前驱与后继进行记录,在对数字串进行操作的时候一并操作。
只需要取以断点或合点为圆心,以 $50$ 为半径的圆内的点的原因
当左侧一个数字的第 \(i-1\) 个后继在断点或合点右侧时这个数字的向后 \(i\) 数字串才会改变,所以左侧只需取 \(49\) 个点。
而即使是断点或合点左侧最靠近断点或合点的数字,也只需要最多 \(49\) 个右侧的数为它做出贡献,所以只需要取圆内的点,而不需要圆上与圆外的点。
由于在查询操作中需要求出数字串的子串,我们将所有对数字串的操作变为字符串操作。为了避免使用一些常数较大的 STL,考虑用数字代表每个字符串,将每个向后数字串哈希起来代表这个数字串,并使用哈希表记录每个数字串的出现次数,在查询操作时查询每个长度为 \(k\) 的子串的哈希值与对应出现次数并进行求积,即可得到答案。
Code
#include<bits/stdc++.h>
using namespace std;
const unsigned long long mdr=998244353,pp=33557999;
unsigned long long n,m,a[200010],pre[200010],nxt[200010],t[200010];
inline int numhash(unsigned long long x){
return x%pp;
}
struct node{
unsigned long long val;
unsigned long long hashv;
int next;
};
struct table{
node data[35000000];
int cnt,head[35000000];
inline unsigned long long& operator [] (unsigned long long x){
int temh=numhash(x);
for(register int i=head[temh];i;i=data[i].next){
if(data[i].val==x)
return data[i].hashv;
}
++cnt;
data[cnt]={x,0,head[temh]};
head[temh]=cnt;
return data[cnt].hashv;
}
}mp,str;
int opt,o,p,pos;
string s;
int main(){
scanf("%llu%llu",&n,&m);
t[0]=1;
for(register int i=1;i<=n;++i){
t[i]=t[i-1]*11;
}
for(register int i=1;i<=n;++i){
scanf("%llu",&a[i]);
++mp[a[i]];
}
for(;m>0;--m){
scanf("%d",&opt);
if(opt==1){
scanf("%d%d",&o,&p);
int lft=49,rgt=50;
unsigned long long tem[150],hs[150];
for(register int i=o;i&&lft;i=pre[i],--lft){
tem[lft]=a[i];
}
++lft;
for(register int i=p;i&&rgt<99;i=nxt[i],++rgt){
tem[rgt]=a[i];
}
--rgt;
for(register int i=lft;i<=rgt;++i){
hs[i]=hs[i-1]*11+tem[i];
}
for(register int i=lft;i<50;++i){
for(register int len=51-i;len<=min(50,rgt-i+1);++len){
int j=i+len-1;
++mp[hs[j]-hs[i-1]*t[len]];
}
}
nxt[o]=p;
pre[p]=o;
}else if(opt==2){
scanf("%d",&o);
p=nxt[o];
int lft=49,rgt=50;
unsigned long long hs[150],tem[150];
for(register int i=o;i&&lft;i=pre[i],--lft){
tem[lft]=a[i];
}
++lft;
for(register int i=p;i&&rgt<99;i=nxt[i],++rgt){
tem[rgt]=a[i];
}
--rgt;
for(register int i=lft;i<=rgt;++i){
hs[i]=hs[i-1]*11+tem[i];
}
for(register int i=lft;i<50;++i){
for(int len=51-i;len<=min(50,rgt-i+1);++len){
int j=i+len-1;
--mp[hs[j]-hs[i-1]*t[len]];
}
}
nxt[o]=0;
pre[p]=0;
}else{
cin>>s;
scanf("%d",&o);
int tem=s.length();
for(int i=0;i<tem;++i){
str[i+1]=str[i]*11+s[i]-'0';
}
unsigned long long ans=mp[str[o]]%mdr;
for(int i=o+1;i<=tem;++i){
ans=ans*mp[str[i]-str[i-o]*t[o]]%mdr;
}
printf("%llu\n",ans);
}
}
return 0;
}
标签:数字串,NOI,int,题解,unsigned,long,或合点,2017,断点
From: https://www.cnblogs.com/blog21012004/p/17995000