链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4129
Haruna每天都会给提督做早餐! 这天她发现早饭的食材被调皮的 Shimakaze放到了一棵
树上,每个结点都有一样食材,Shimakaze要考验一下她。
每个食材都有一个美味度,Shimakaze会进行两种操作:
1、修改某个结点的食材的美味度。
2、对于某条链,询问这条链的美味度集合中,最小的未出现的自然数是多少。即mex值。
请你帮帮Haruna吧。
第一行包括两个整数n,m,代表树上的结点数(标号为1~n)和操作数。
第二行包括n个整数a1...an,代表每个结点的食材初始的美味度。
接下来n-1行,每行包括两个整数u,v,代表树上的一条边。
接下来m 行,每行包括三个整数
0 u x 代表将结点u的食材的美味度修改为 x。
1 u v 代表询问以u,v 为端点的链的mex值。
对于每次询问,输出该链的mex值。
10 10
1 0 1 0 2 4 4 0 1 0
1 2
2 3
2 4
2 5
1 6
6 7
2 8
3 9
9 10
0 7 14
1 6 6
0 4 9
1 2 2
1 1 8
1 8 3
0 10 9
1 3 5
0 10 0
0 7 7
0
1
2
2
3
1<=n<=5*10^4
1<=m<=5*10^4
0<=ai<=10^9
分析:
带修莫队+树形莫队
将这个数分块,利用深搜的方法入栈和出栈,通过修改的时间来实现修改,查询mex,也需要将自然数分块,看谁不满足条件,代码里有详细的解析不说了
代码:
#include<stdio.h>
#include<algorithm>
#include<math.h>
#define go(i,a,b) for(int i=a;i<=b;i++)
#define ro(i,a,b) for(int i=a;i>=b;i--)
#define fo(i,a,x) for(int i=a[x],v=e[i].v;i;i=e[i].next,v=e[i].v)
using namespace std;const int N=50009;
struct E{int v,next;}e[N*3];
int k=1,head[N],unit,Be[N],m,st[N],top,fa[N][18],deep[N];
int n,Q,a[N],t[N],op,x,y,p,tim,u=1,v=1,T,ans[N],vis[N];
void ADD(int u,int v)
{
e[k]=(E){v,head[u]};
head[u]=k++;
}
void dfs(int u){
go(i,1,19)
{
if((1<<i)>deep[u])
break;
else
fa[u][i]=fa[fa[u][i-1]][i-1];///记录祖先,从离它最近的祖先开始到远的
}
int bottom=top;
fo(i,head,u)
if(v!=fa[u][0])
{
fa[v][0]=u;
deep[v]=deep[u]+1;
dfs(v);
if(top-bottom>=unit)
{
m++;
while(top!=bottom)
Be[st[top--]]=m;
}///记录某一点分到了那一个块
}
st[++top]=u;
}
int LCA(int x,int y)///x,y的最近公共祖先
{
if(deep[x]<deep[y])
swap(x,y);
int Dis=deep[x]-deep[y];
go(i,0,16)
if((1<<i)&Dis)x=fa[x][i];///找到跟y同一层的点
if(x==y)return x;
ro(i,16,0)
{
if(fa[x][i]!=fa[y][i])///缩短两点的距离,使他们离最近公共祖先越来越近
x=fa[x][i],y=fa[y][i];
}
return x==y?x:fa[x][0];
}
struct Change{int u,New,Old;}cq[N];
struct Query{int u,v,tim,id;
bool operator <(const Query &a) const{
return Be[u]==Be[a.u]?(Be[v]==Be[a.v]?tim<a.tim:Be[v]<Be[a.v]):Be[u]<Be[a.u];
}}q[N];
struct Datalock{
struct _blo{int l,r;}b[350];///新的块,每一个块就是连续的自然数的左端点和右端点
int n,Be[N],m,unit,num[N],sum[350];
void init()
{
unit=sqrt(n);
m=(n-1)/unit+1;
go(i,1,n)Be[i]=(i-1)/unit+1;
go(i,1,m)b[i].l=(i-1)*unit+1,b[i].r=i*unit;
b[m].r=n;
}
void Add(int v){if(v<=n)sum[Be[v]]+=(++num[v])==1;}
void Del(int v){if(v<=n)sum[Be[v]]-=(--num[v])==0;}
int mex()///求区间的mex
{
go(i,1,m)if(sum[i]!=b[i].r-b[i].l+1)///sum是这个块的数的个数,判断是否等于r-l+1来判断是否有不出现的数
go(j,b[i].l,b[i].r)if(!num[j])return j;
return -1;
}
}Data;
void revise(int u,int d)
{
if(vis[u])
Data.Del(a[u]),Data.Add(d);
a[u]=d;
}
void Run(int u){
if(vis[u])
Data.Del(a[u]),vis[u]=0;
else
Data.Add(a[u]),vis[u]=1;
}
void move(int x,int y)///从x点移动到y点
{
if(deep[x]<deep[y])swap(x,y);
while(deep[x]>deep[y])
Run(x),x=fa[x][0];
while(x!=y)Run(x),Run(y),x=fa[x][0],y=fa[y][0];
}
void Mo()
{
go(i,1,p)
{
while(T<q[i].tim)T++,revise(cq[T].u,cq[T].New);
while(T>q[i].tim)revise(cq[T].u,cq[T].Old),T--;
if(u!=q[i].u)move(u,q[i].u),u=q[i].u;
if(v!=q[i].v)move(v,q[i].v),v=q[i].v;
int anc=LCA(u,v);
Run(anc);///最近公共祖先走两次相当于没走,所以要加上
ans[q[i].id]=Data.mex()-1;///因为初始的值都加了1,所以要减1
Run(anc);///还原
}
}
int main()
{
scanf("%d%d",&n,&Q);
unit=pow(n,0.45);
go(i,1,n)scanf("%d",&a[i]),t[i]=++a[i];
go(i,2,n){
int uu,vv;
scanf("%d%d",&uu,&vv);
ADD(uu,vv);
ADD(vv,uu);
}
dfs(1);
while(top)
Be[st[top--]]=m;
go(i,1,Q)
{
scanf("%d%d%d",&op,&x,&y);
if( op)
p++,q[p]=(Query){x,y,tim,p};
if(!op)
tim++,cq[tim]=(Change){x,y+1,t[x]},t[x]=y+1;
}
Data.n=n+1;
Data.init();
sort(q+1,q+1+p);///将查询排序
Mo();
go(i,1,p)printf("%d\n",ans[i]);
}//Paul_Guderian