首页 > 其他分享 >P5537 【XR-3】系统设计 题解-哈希+线段树二分

P5537 【XR-3】系统设计 题解-哈希+线段树二分

时间:2023-10-26 18:22:38浏览次数:40  
标签:return P5537 val int 题解 哈希 fi pll

20231026

P5537 【XR-3】系统设计 题解-哈希+线段树二分

这个东西怎么会和哈希有关?!直接寄。

Statement

这个系统首先需要输入一棵 \(n\) 个点的有根树和一个长度为 \(m\) 的序列 \(a\),接下来需要实现 \(q\) 个操作。

操作分两种:

  1. 1 x l r 表示设定起点为有根树的节点 \(x\),接下来依次遍历 \(l \sim r\)。当遍历到 \(i\) 时,从当前节点走向它的编号第 \(a_i\) 小的儿子。如果某一时刻当前节点的儿子个数小于 \(a_i\),或者已经遍历完 \(l \sim r\),则在这个点停住,并输出这个点的编号,同时停止遍历。
  2. 2 t k 表示将序列中第 \(t\) 个数 \(a_t\) 修改为 \(k\)。

\(1\le n,m,q\le 5 \times 10^5\)。

Solution

看了题之后根本没什么思路,

知道用哈希维护的时候还是没有什么思路。。。

终于用一个小时看懂了这道题,于是写一篇题解。

首先发现题目中的查询一定是走到不能走为止,

那么我们就希望知道这个最远的节点。

因为树的形态是不变的,所以我们从根走到这个节点的路径也是不变的。

也就是说存在唯一的一个序列,表示从根走到节点 \(u\) 的路径,

其中的每一个元素就代表走的第几小的边。

于是我们可以把这个序列变成字符串去维护。

用样例的那棵树理解一下:

![pic1](E:\H_W_Y\0-C++#代码编程#2023编程\2023秋成都七中\10.26-Balance Tree\pic1.png)

我们令序列为 \(s_i\) ,于是在这个图中,有:

\[s_1=\\ s_2=1\\ s_3=11\\ s_4=12\\ s_5=2\\ s_6=21 \]

举例解释一下:\(s_4=12\) 就表示从根节点 \(1\) 开始走到它第一小的儿子节点 \(2\),再走 \(2\) 的第二小的儿子节点即可到达 \(4\)。

这样一来,我们就可以用题目中的 \(a\) 数组的表达方式表示出了到每一个节点的位置,而在预处理的时候,我们可以直接维护它的哈希值,从父亲转移到儿子即可。

于是这个序列就和 \(a\) 数组本质相同了。

而按照同样的表达方式,

对于每一次询问,我们把 \(s_x\) 和 \(a_{l\dots r}\) 给合并起来,

这就是理想的序列,

而答案就是在这个序列中能匹配最多的那个节点。

具体来说对于样例里面的第一个询问 1 1 1 3

把它转化成字符串就是 \(122\),我们再用 \(s\) 去进行匹配

匹配到最后面的点是 \(s_4=12\),这就是这次询问的答案。

还是比较好理解。

于是查询的思路有了,那我们怎么实现呢?

不难想到可以用字符串哈希完成询问的比较,

而我们可以用二分答案。

由于还涉及到单点修改,不难想到可以用线段树去维护区间的 \(a\) 。

但是发现如果直接二分会超时,于是可以把二分融入线段树的查询中。

具体来说就是在查询的过程中,

每一次合并两个区间的时候,先考虑左子树的哈希是否有匹配的点,

如果存在就遍历右子树,

反之遍历左子树即可。

具体可以看代码理解。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define fi first
#define se second
#define pll pair<ll,ll>
#define mk make_pair
#define pb push_back

const int N=1e6+5;
int n,m,q,a[N],rt;
vector<int> g[N];

int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
} 

void print(int x){
  int p[15],tmp=0;
  if(x==0) putchar('0');
  if(x<0) putchar('-'),x=-x;
  while(x){p[++tmp]=x%10;x/=10;}
  for(int i=tmp;i>=1;i--) putchar(p[i]+'0');putchar('\n');
} 

namespace Hash{
  const int P=1e6-17,s=24601;
  int tot=0,head[N];
  struct node{
  	pll val;
  	int nxt,w;
  }e[N];//链式前向星去存储哈希以完成比较
  void add(int u,pll val,int w){
  	e[++tot]=(node){val,head[u],w};
  	head[u]=tot;
  }
  void ins(pll a,int id){
  	int tmp=(a.fi*s+a.se)%P;
  	add(tmp,a,id);
  } 
  int qry(pll a){
  	int tmp=(a.fi*s+a.se)%P;
  	for(int i=head[tmp];i;i=e[i].nxt)
  	  if(e[i].val==a) return e[i].w;
  	return -1;
  }
}

pll ph[N],hs[N];//双哈希数组,维护每一个点的s
const pll base={114514,1919810},mod={1e9+7,998244353}; 

pll operator +(const pll &a,const pll &b){return {(a.fi+b.fi)%mod.fi,(a.se+b.se)%mod.se};}
pll operator *(const pll &a,const pll &b){return {(a.fi*b.fi)%mod.fi,(a.se*b.se)%mod.se};}

void init(){
  ph[0]=mk(1,1);
  for(int i=1;i<=n;i++) ph[i]=ph[i-1]*base;
}

void dfs(int u){
  sort(g[u].begin(),g[u].end());
  int cnt=0;
  for(auto v:g[u]){
  	cnt++;
  	hs[v]=hs[u]*base+mk(cnt,cnt);
    dfs(v);
  }
}

#define mid ((l+r)>>1)
#define lson l,mid,p<<1
#define rson mid+1,r,p<<1|1
#define lc p<<1
#define rc p<<1|1
pll tr[N<<2];
  
void pu(int l,int r,int p){tr[p]=(tr[lc]*ph[r-mid]+tr[rc]);}
void build(int l,int r,int p){
  if(l==r){
  	tr[p]={1ll*a[l],1ll*a[l]};
	return;	
  }
  build(lson);build(rson);
  pu(l,r,p);
}

void update(int l,int r,int p,int x,ll val){
  if(l==r){
  	tr[p]={val,val};
	return;	
  }
  if(x<=mid) update(lson,x,val);
  else update(rson,x,val);
  pu(l,r,p);
}

pll query(int l,int r,int p,int x,int y){
  if(x<=l&&y>=r) return tr[p];
  if(y<=mid) return query(lson,x,y);
  if(x>mid) return query(rson,x,y);
  return query(lson,x,y)*ph[min(r,y)-mid]+query(rson,x,y);
}

int ask(int l,int r,int p,int x,int y,pll val){
  if(l==r) return Hash::qry(val*base+tr[p]); 
  if(y<=mid) return ask(lson,x,y,val);
  if(x>mid) return ask(rson,x,y,val);
  pll h=val*ph[mid-max(l,x)+1]+((x>l)?query(l,r,p,x,mid):tr[lc]);
  int tmp=Hash::qry(h);
  if(tmp==-1) return ask(lson,x,y,val);
  else {
  	int res=ask(rson,x,y,h);
	return (res!=-1)?res:tmp;	
  }
}

int main(){
  freopen("P5537.in","r",stdin);
  freopen("P5537.out","w",stdout);
  /*2023.10.26 H_W_Y P5537 【XR-3】系统设计 Hash+线段树二分*/ 
  n=read();m=read();q=read();
  init();
  for(int i=1;i<=n;i++){
  	int x=read();
  	if(x) g[x].pb(i);
    else rt=i;
  }
  hs[rt]=mk(0,0);dfs(rt);
  for(int i=1;i<=n;i++) Hash::ins(hs[i],i);
  for(int i=1;i<=m;i++) a[i]=read();
  build(1,m,1);
  for(int i=1;i<=q;i++){
  	int op=read();
  	if(op==1){
  	  int x=read(),l=read(),r=read();
	  int tmp=ask(1,m,1,l,r,hs[x]);
	  print((tmp==-1)?x:tmp);	
	}
	else {
	  int x=read(),val=read();
	  update(1,m,1,x,val);
	}
  }
  return 0;
}

Conclusion

树上的路径走法问题我们可以用字符串维护,再用哈希比较。

标签:return,P5537,val,int,题解,哈希,fi,pll
From: https://www.cnblogs.com/hwy0622/p/P5537.html

相关文章

  • CF888E题解
    分析看到\(n\leq35\)的数据范围就想到了meet-in-middle。先爆搜出对于\(1\sim\frac{n}{2}\)和\(\frac{n}{2}\simn\)两个下标范围内在模意义下所有的和。然后用一个常见trick,就是枚举第二个部分的和,然后匹配第一个部分的和的最优解。显然匹配有两种情况,一种是......
  • chrome新版本http自动跳转https问题解决
    虽然是个好功能,但是部分内网业务还没做好https兼容,有的时候手工访问,还是变成https 解决办法:chrome://flags/找到:HTTPSUpgrades,修改disabled,重启解决,当然这个需要需要用户去调整,真正还需要服务去兼容https  ......
  • 在使用 Unity 2022 打包安卓项目时,遇到 gradle 无法访问或下载超级慢最终超时出错的问
    一般表现是打包最后一步会等待超长时间,最后报错,错误信息:PickedupJAVA_TOOL_OPTIONS:-Dfile.encoding=UTF-8FAILURE:Buildfailedwithanexception.*Whatwentwrong:Aproblemoccurredconfiguringrootproject'Gradle'.>Couldnotresolveallartifactsfor......
  • CCC 2023 题解 和 思考过程
    Trianglane水题,只要分情况判断中间和两侧有边叠牢的情况,每次减2#include<iostream>#include<cstdlib>#include<cstdio>#include<cmath>#include<algorithm>#include<cstring>usingnamespacestd;typedeflonglongll;constintN=200010......
  • 「题解」Codeforces Round 905 (Div. 3)
    before终于有一篇题解是一次性更所有题的了。A.MorningProblemA.MorningSol&Code根据题意模拟即可。#include<bits/stdc++.h>typedeflonglongll;intmin(inta,intb){returna<b?a:b;}intmax(inta,intb){returna>b?a:b;}intT;int......
  • CF888C题解
    分析容易想到可以枚举每个字母,分别求其最小\(k\)取\(\min\)。思考对于一个\(k\),如何判其不合法。容易想到如果存在一个没有这个字符的长度大于等于\(k\)的子段,那么这个\(k\)就不合法。那么我们就知道如何求最小合法\(k\)了。找到最长的没有这个字符的子段,其长度加......
  • CF888A题解
    分析因为一个数不可能同时大于并小于它两边的数,所以两种数的集合不存在交集。所以分别扫一遍两种数的个数加在一起即可。代码#include<iostream>usingnamespacestd;constexprintMAXN(1000007);inta[MAXN];intn,ans;inlinevoidread(int&temp){cin>>t......
  • Redisson分布式锁主从一致性问题解决
    Redis联锁联锁(RedissonMultiLock)对象可以将多个RLock对象关联为一个联锁,实现加锁和解锁功能。每个RLock对象实例可以来自于不同的Redisson实例。如果负责储存分布式锁的某些Redis节点宕机以后,而且这些锁正好处于锁住状态,就会出现死锁问题。为了避免这种情况的发生,Redisson内部提供......
  • AGC304Ex Constrained Topological Sort 题解
    AT一个直接的想法是拓扑排序时从小到大标号:每次在入度为\(0\)的点中找到\(l_{u}\lei\)且\(r_{u}\)最小的\(u\),令\(p_{u}=i\)问题是如果\(r_{u}\)很大,那么\(u\)被标号的优先级很低,会连累\(u\)的后继中\(r\)较小的点做法是倒着拓扑一遍,令\(r_{u}\leftarrow\m......
  • [ARC164D]1D Coulomb 题解
    [ARC164D]1DCoulomb题解题意在长为\(2N\)的数轴上放有\(2N\)个小球,第\(i\)个小球在坐标\(i\)的位置。\(2N\)个小球中有\(N\)个小球带正电,有\(N\)个小球带负点。记第\(i\)个小球带\(a_i\)单位电荷(\(a_i\in\{1,-1\}\)),小球之间受到力的作用,第\(i\)个小球受到......