BZOJ 4129: Haruna’s Breakfast 【树上莫队】

本文探讨了如何运用莫队算法和树链剖分解决复杂数据结构问题,详细解析了一种寻找最小未出现自然数的算法,并通过实例演示了如何优化算法复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述:

中文题面直接上传送门

题目分析:

第一道带修莫队,放礼花~
这道题按照常理来说可以树链剖分+线段树 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

用莫队的话得想想怎么维护最小的未出现的自然数
我最开始想的是用树状数组维护每个数前面的自然数的个数,每次修改 l o g n logn logn,查询就二分x,看x前面的数的个数是否是x-1, l o g 2 n log^2n log2n,总复杂度 O ( n 5 3 l o g n + m l o g 2 n ) O(n^{\frac 53}logn+mlog^2n) O(n35logn+mlog2n)

但是莫队的修改数量是肯定大于查询数量的,所以要让修改复杂度降为 O ( 1 ) O(1) O(1),查询就改用分块维护(对权值分块) O ( n ) O(\sqrt n) O(n ),总复杂度 O ( n 5 3 + m n ) O(n^{\frac 53}+m\sqrt n) O(n35+mn )

Code:

#include<cstdio>
#include<cctype>
#include<algorithm>
#define maxn 50005
#define S 600
using namespace std;
inline void read(int &a){
	char c;while(!isdigit(c=getchar()));
	for(a=c-'0';isdigit(c=getchar());a=a*10+c-'0');
}
int n,m,stk[maxn],Tp,bel[maxn],block,a[maxn],b[maxn],A[maxn],B[maxn];
int fa[maxn],dep[maxn],son[maxn],siz[maxn],top[maxn];
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
int ans[maxn];
bool vis[maxn];
struct Query{
	int x,y,t,id;
	bool operator < (const Query &p)const{return bel[x]==bel[p.x]?bel[y]==bel[p.y]?t<p.t:bel[y]<bel[p.y]:bel[x]<bel[p.x];}
}q[maxn];
struct Change{
	int pos,x,y;
}C[maxn];
inline void line(int x,int y){nxt[++tot]=fir[x];fir[x]=tot;to[tot]=y;}
void dfs1(int u){
	siz[u]=1;
	int cur=Tp;
	for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=fa[u]){
		fa[v]=u,dep[v]=dep[u]+1;
		dfs1(v);siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
		if(Tp-cur>=S){
			++block;
			while(Tp>cur) bel[stk[Tp--]]=block;
		}
	}
	stk[++Tp]=u;
}
void dfs2(int u,int tp){
	top[u]=tp;
	if(son[u]) dfs2(son[u],tp);
	for(int i=fir[u];i;i=nxt[i]) if(!top[to[i]]) dfs2(to[i],to[i]);
}
int LCA(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
}
void upd(int x){
	if(a[x]>n) return;
	if(vis[x]) {if(--A[a[x]]==0) B[a[x]/S]--;}
	else {if(A[a[x]]++==0) B[a[x]/S]++;}
	vis[x]=!vis[x];
}
void modify(int pos,int x){
	if(!vis[pos]) a[pos]=x;
	else upd(pos),a[pos]=x,upd(pos);
}
void rev(int u,int v){
	while(u!=v){
		if(dep[u]<dep[v]) swap(u,v);
		upd(u),u=fa[u];
	}
}
int solve(){
	int i=0;while(B[i]==S) i++;
	i*=S;while(A[i]) i++;
	return i;
}
int main()
{
	int op,x,y,t,qt=0,ct=0,lca;
	read(n),read(m);
	for(int i=1;i<=n;i++) read(a[i]),b[i]=a[i];
	for(int i=1;i<n;i++) read(x),read(y),line(x,y),line(y,x);
	dfs1(1);++block;while(Tp) bel[stk[Tp--]]=block;
	dfs2(1,1);
	for(int i=1;i<=m;i++){
		read(op),read(x),read(y);
		if(op) q[++qt]=(Query){x,y,ct,qt};
		else C[++ct]=(Change){x,b[x],y},b[x]=y;
	}
	sort(q+1,q+1+qt);
	x=q[1].x,y=q[1].x,t=0;
	for(int i=1;i<=qt;i++){
		while(t<q[i].t) t++,modify(C[t].pos,C[t].y);
		while(t>q[i].t) modify(C[t].pos,C[t].x),t--;
		rev(x,q[i].x),rev(y,q[i].y),upd(lca=LCA(x=q[i].x,y=q[i].y));
		ans[q[i].id]=solve(),upd(lca);
	}
	for(int i=1;i<=qt;i++) printf("%d\n",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值