4129: Haruna’s Breakfast【带修莫队+树形莫队】

链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4129

Description

 Haruna每天都会给提督做早餐! 这天她发现早饭的食材被调皮的 Shimakaze放到了一棵

树上,每个结点都有一样食材,Shimakaze要考验一下她。

每个食材都有一个美味度,Shimakaze会进行两种操作:

1、修改某个结点的食材的美味度。

2、对于某条链,询问这条链的美味度集合中,最小的未出现的自然数是多少。即mex值。

请你帮帮Haruna吧。

Input

第一行包括两个整数n,m,代表树上的结点数(标号为1~n)和操作数。

第二行包括n个整数a1...an,代表每个结点的食材初始的美味度。

接下来n-1行,每行包括两个整数u,v,代表树上的一条边。

接下来m 行,每行包括三个整数

0 u x 代表将结点u的食材的美味度修改为 x。

1 u v 代表询问以u,v 为端点的链的mex值。

Output

对于每次询问,输出该链的mex值。

Sample Input

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

Sample Output

0
1
2
2
3

HINT

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值