人渣的本愿(hanabi)

人渣的本愿

题目描述

给定一棵 \(n\) 个点根为 \(1\) 号节点的树,每一个节点 \(i\) 都有一个二元组信息 \((a_i,b_i)\),初始时全部为 \((0,0)\)

接下来有 \(m\) 次操作,每次操作为以下两种操作中的一种:

  • 1 x c 修改从 \(x\) 到根节点的路径上的点的二元组信息。具体地,设当前为第 \(z\) 次操作,对于一个路径上的点 \(u\),若 \(a_u=c\)\((a_u,b_u)\) 不变,否则 \((a_u,b_u)\gets(c,z)\)
  • 2 x 查询 \((a_x,b_x)\)

数据范围

\(1\le n,m\le10^6\)\(1\le x,c\le n\)

时空限制

\(2\text{s}\)\(512\text{Mb}\)

做法

提供一个 \(O(q\log^2 n)\) 的做法,赛时斩获最慢解。

为了方便表达,我们约定 \((a_i,b_i)\)\(a_i\) 为点 \(i\) 的颜色,\(b_i\) 为点 \(i\) 的时间。

首先对树进行重剖,修改的时候跳重链修改。可以看出修改操作类似于一个推平,我们考虑拿线段树做。

乍一看线段树似乎无法做这样的区间推平,因为需要进行操作的区间里有的点需要改,有的不需要。可以发现如果线段树上有一个被当前修改区间覆盖的区间整个区间的颜色都一样,那么只有以下两种情况:

  1. 这个区间里的点都不需要改(这个区间内所有点的颜色都与 \(c\) 相等);
  2. 这个区间里每个点的二元组都需要推平成 \((c,z)\)

无论是上述哪一种情况,我们都可以 \(O(1)\) 处理整个区间,对于情况 \(1\),我们直接 return,对于情况 \(2\),我们可以打标记。所以我们记录一下这个区间里颜色的最大值,颜色的最小值,以便于判断整个区间是否只有一种颜色。只有遇到上述这样的区间我们才 return(显然叶子结点是满足上述要求的,所以不用担心似掉),否则就暴力往下递归修改。

这样我们就得到了一个能过但复杂度我不是很会算的算法,感性理解一下,这个线段树的功能比较像珂朵莉树,所以其时间复杂度应为 \(O(q\log^2n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int dfn[N],fa[N],cnt,son[N],siz[N],tp[N];
vector<int> v[N];
struct node{
	int l,r;
	int minc,maxc,mino,maxo,tagc,tago;
}tr[N<<2];
void pushup(int p){
	tr[p].minc=min(tr[p<<1].minc,tr[p<<1|1].minc);
	tr[p].maxc=max(tr[p<<1].maxc,tr[p<<1|1].maxc);
	tr[p].mino=min(tr[p<<1].mino,tr[p<<1|1].mino);
	tr[p].maxo=max(tr[p<<1].maxo,tr[p<<1|1].maxo);
}
void spread(int p){
	if(tr[p].tagc){
		tr[p<<1].tagc=tr[p].tagc;
		tr[p<<1|1].tagc=tr[p].tagc;
		tr[p<<1].minc=tr[p<<1].maxc=tr[p].tagc;
		tr[p<<1|1].minc=tr[p<<1|1].maxc=tr[p].tagc;
		tr[p].tagc=0;
	}
	if(tr[p].tago){
		tr[p<<1].tago=tr[p].tago;
		tr[p<<1|1].tago=tr[p].tago;
		tr[p<<1].mino=tr[p<<1].maxo=tr[p].tago;
		tr[p<<1|1].mino=tr[p<<1|1].maxo=tr[p].tago;
		tr[p].tago=0;
	}
}
void build(int p,int l,int r){
	tr[p].l=l,tr[p].r=r;
	if(l==r) return;
	int mid=l+r>>1;
	build(p<<1,l,mid),build(p<<1|1,mid+1,r);
}
void change(int p,int l,int r,pair<int,int> k){
	if(tr[p].l>=l&&tr[p].r<=r&&tr[p].minc==tr[p].maxc){
		if(tr[p].minc==k.first) return;
		tr[p].minc=tr[p].maxc=tr[p].tagc=k.first;
		tr[p].mino=tr[p].maxo=tr[p].tago=k.second;
		return;
	}
	spread(p);
	int mid=tr[p].l+tr[p].r>>1;
	if(l<=mid) change(p<<1,l,r,k);
	if(r>mid) change(p<<1|1,l,r,k);
	pushup(p);
}
pair<int,int> query(int p,int x){
	if(tr[p].l<=x&&tr[p].r>=x&&tr[p].minc==tr[p].maxc&&tr[p].mino==tr[p].maxo) return {tr[p].minc,tr[p].mino};
	int mid=tr[p].l+tr[p].r>>1;
	spread(p);
	if(x<=mid) return query(p<<1,x);
	else return query(p<<1|1,x);
}
void dfs1(int u){
	siz[u]=1;
	int maxx=-1;
	for(auto i:v[u])
		if(i!=fa[u]){
			dfs1(i);
			siz[u]+=siz[i];
			if(maxx<siz[i]) maxx=siz[i],son[u]=i;
		}
	siz[u]++;
}
void dfs2(int u,int Tp){
	tp[u]=Tp;
	dfn[u]=++cnt;
	if(son[u]) dfs2(son[u],Tp);
	for(auto i:v[u])
		if(i!=fa[u]&&i!=son[u])
			dfs2(i,i);
}
void modify(int x,pair<int,int> k){
	while(x){
		change(1,dfn[tp[x]],dfn[x],k);
		x=fa[tp[x]];
	}
}
int n,m;
int main(){
//	freopen("hanabi.in","r",stdin);
//	freopen("hanabi.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=2;i<=n;i++){
		cin>>fa[i];
		v[fa[i]].emplace_back(i),v[i].emplace_back(fa[i]);
	}
	dfs1(1);
	dfs2(1,1);
	build(1,1,n);
	cnt=0;
	while(m--){
		cnt++;
		int op,x,c;
		cin>>op>>x;
		if(op==2){
			pair<int,int> ans=query(1,dfn[x]);
			cout<<ans.first<<' '<<ans.second<<'\n';
		}
		else{
			cin>>c;
			modify(x,{c,cnt});
		}
	}
}
posted @ 2025-05-27 16:41  Fiendish  阅读(268)  评论(0)    收藏  举报