【Comet OJ - Contest #9 & X Round 3】系统设计(树链剖分+线段树维护哈希)

算法康复训练 专栏收录该内容
17 篇文章 0 订阅

题目链接

先进行树链剖分,每次询问时从起点开始,如果是走向轻儿子则直接走,如果是走向重儿子则走到这段重链能走到的最远处,这样只会走 O ( l o g n ) O(logn) O(logn)次,问题就是怎么找重链上能走到的最远处。定义一个点的代号是它在父亲那的排位,那么问题转化成求从一个点出发由重链向下的最远点,使得路径上点的代号恰好能与序列中的一段匹配。

 

Sol1:
由于退役太久已经没啥OI思维了,想了一个蠢办法qwq。
对每个点维护它所在重链向下走 2 k 2^k 2k步的后代以及所得代号串的哈希值,同时维护序列的哈希值。每次走重链时匹配所有 2 k 2^k 2k长度即可。由于带修改,序列哈希值维护需要一棵线段树,所以单次询问复杂度是 O ( l o g 3 n ) O(log^3n) O(log3n),开始用unsigned long long超时了,改成unsigend int之后很勉强地艹过去了qwq。
【UPD:发现我哈希用的进制数太小,所以很容易冲突,导致每次重链跳不干净…把进制数换成大于n之后,用unsigned long long也可以过了qwq。】

 

Sol2:
事实上可以让同一条重链的点在DFS序内的连续一段,这样的话重链上某两点间代号串的哈希值用前缀和就 O ( 1 ) O(1) O(1)搞定了。走重链时在线段树上二分。这样单次询问是 O ( l o g 2 n ) O(log^2n) O(log2n)。重链在DFS序内连续一段,这种以前十分熟练的树剖基操,现在竟然完全想不起来了qwq。

 

Sol3:
官方正解:设序列是A,root到x的代号串是S[x],找到最大的p<=r,使得树中存在一点u,满足S[x]+A[l…p] == S[u],则u就是答案。把所有S[x]的哈希值存下来,线段树上二分就行。
可以用哈希表做到 O ( 1 ) O(1) O(1)查询某哈希值是否存在,从而单次询问 O ( l o g n ) O(logn) O(logn)。我直接用unordered_map存哈希值发现TLE了…不知道是不是写错了qwq。

 

Code Sol1:

#pragma GCC optimize(2)
#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
#include<string.h>
#include<set>
#define LL long long
using namespace std;
void write(int x)
{
  if(x<0) x=-x,putchar('-');
  if(x>9) write(x/10);
  putchar(x%10+'0');
}
void read(LL& sum)
{
  sum=0;char c=getchar( );bool f=0;
  while(c<'0' || c>'9') {if(c=='-') f=1;c=getchar( );}
  while(c>='0' && c<='9') {sum=sum*10+c-'0';c=getchar( );}
  if(f) sum=-sum;
}
void read(int& sum)
{
  sum=0;char c=getchar( );bool f=0;
  while(c<'0' || c>'9') {if(c=='-') f=1;c=getchar( );}
  while(c>='0' && c<='9') {sum=sum*10+c-'0';c=getchar( );}
  if(f) sum=-sum;
}
#define ULL unsigned int
const int N=500005;
int n,m,q,rt,a[N],p[N];
vector<int>S[N];
int sz[N],son[N][20];
ULL H[N][20];
ULL HS[N];
void DFS(int k,int f)
{
  int i,x,y,z,mx=0;
  if(!p[k])
    {
      for(i=0;i<=19;i++)
		son[k][i]=k,H[k][i]=0;
      sz[k]=1;
      return;
    }
  for(i=0;i<p[k];i++)
    {
      x=S[k][i];
      if(x==f) continue;
      DFS(x,k);
      sz[k]+=sz[x];
      if(sz[x]>mx) mx=sz[x],y=x,z=i+1;
    }
  sz[k]++;
  son[k][0]=y;H[k][0]=z;
  for(i=1;i<=19;i++)
    {
      son[k][i]=son[son[k][i-1]][i-1];
      H[k][i]=H[k][i-1]*HS[1<<(i-1)]+H[son[k][i-1]][i-1];
    }
}
ULL P[N*4];
void build(int x,int l,int r)
{
  if(l==r) {P[x]=a[l];return;}
  int mid=(l+r)>>1,L=(x<<1),R=L+1;
  build(L,l,mid);
  build(R,mid+1,r);
  P[x]=P[L]*HS[r-mid]+P[R];
}
int ps,val;
void ch(int x,int l,int r)
{
  if(l==r) {P[x]=val;return;}
  int mid=(l+r)>>1,L=(x<<1),R=L+1;
  if(ps<=mid) ch(L,l,mid);
  else ch(R,mid+1,r);
  P[x]=P[L]*HS[r-mid]+P[R];
}
void modify(int x,int v) {ps=x;val=v;ch(1,1,m);}
ULL ask(int x,int l,int r,int nl,int nr)
{
  if(l==nl&&r==nr) return P[x];
  int mid=(l+r)>>1,L=(x<<1),R=L+1;
  if(nr<=mid) return ask(L,l,mid,nl,nr);
  else if(nl>mid) return ask(R,mid+1,r,nl,nr);
  else return ask(L,l,mid,nl,mid) * HS[nr-mid] + ask(R,mid+1,r,mid+1,nr);
}	 
int main( )
{
  int i,j,x,l,r;
  read(n);read(m);read(q);
  HS[0]=1;
  for(i=1;i<N;i++) HS[i]=HS[i-1]*N;
  for(i=1;i<=n;i++)
    {
      read(x);
      if(x) S[x].push_back(i);
      else rt=i;
    }
  for(i=1;i<=m;i++) read(a[i]);
  for(i=1;i<=n;i++) p[i]=S[i].size( ),sort(S[i].begin( ),S[i].end( ));
  DFS(rt,0);build(1,1,m);
  while(q--)
    {
      read(x);
      if(x==1)
		{
		  read(x);read(l);read(r);
		  while(l<=r)
		    {
		      if(p[x]<a[l]) break;
		      if(a[l]!=H[x][0]) x=S[x][a[l]-1],l++;
		      else
				{
				  for(int i=19;i>=0;i--)
				    if(l+(1<<i)-1<=r && ask(1,1,m,l,l+(1<<i)-1)==H[x][i])
				      l+=(1<<i),x=son[x][i];
				}
		    }
		  write(x);puts("");
		}
      else
		{
		  read(x);read(l);
		  a[x]=l;modify(x,l);
		}
    }
  return 0;
}

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值