【GDOI2017模拟12.9】最近公共祖先

题目大意

给定一个n个节点的有根树,每个节点有一个权值s[i],且有颜色(最初全是白色)。有m次操作,分两种:
1. 把一个节点染黑。
2. 询问一个节点x,枚举每个黑色节点y,得到lca(x,y)=z,求所有可能的z中最大的权值

n≤100000 m≤200000

分析

一个思路:
维护f[i]表示以i节点为根的子树中有多少个节点,当询问节点x的时候,暴力枚举它和它的所有祖先,如果枚举到一个i,它的f[x]和f[father(i)]不相等,那么一定存在一个黑色节点和x的lca为father(i)。(这个比较显然)
那么修改就是暴力给x和它的父亲全部f[i]++

观察这个过程,如果对于一个节点x,它第一次f[father(x)]>f[x],那么以后就一定会保持这个状态。那么一个算法流程出来了:
修改操作时,首先给它的子树的答案与s[x]取max,然后暴力往上跑,跑到一个节点x,给father(x)子树中除了x这个子树外的所有节点答案与s[x]取max。然后如果节点father(x)被访问过了,就表示f[father(x)]>f[x],就可以不继续往上跑了。

搞出dfn,然后线段树修改+查询。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int N=100005,M=200005,T=262200;

typedef long long LL;

int n,m,tot,h[N],e[M],nxt[M],s[N],fa[N],Size[N],dfn[N],t[T];

bool tu_color[N],visit[N];

char c;

int read()
{
    for (c=getchar();c<'0' || c>'9';c=getchar());
    int x=c-48;
    for (c=getchar();c>='0' && c<='9';c=getchar()) x=x*10+c-48;
    return x;
}

void add(int x,int y)
{
    e[++tot]=y; nxt[tot]=h[x]; h[x]=tot;
}

void dfs(int x)
{
    dfn[x]=++tot;
    for (int i=h[x];i;i=nxt[i]) if (e[i]!=fa[x])
    {
        fa[e[i]]=x;
        dfs(e[i]);
        Size[x]+=Size[e[i]]+1;
    }
}

void push_down(int x)
{
    if (t[x]>0)
    {
        t[x<<1]=max(t[x<<1],t[x]);
        t[x<<1|1]=max(t[x<<1|1],t[x]);
        t[x]=0;
    }
}

void change(int l,int r,int a,int b,int v,int x)
{
    if (l==a && r==b)
    {
        t[x]=max(t[x],v);
        return;
    }
    push_down(x);
    int mid=l+r>>1;
    if (b<=mid) change(l,mid,a,b,v,x<<1);
    else if (a>mid) change(mid+1,r,a,b,v,x<<1|1);
    else
    {
        change(l,mid,a,mid,v,x<<1); change(mid+1,r,mid+1,b,v,x<<1|1);
    }
}

int query(int l,int r,int g,int x)
{
    if (l==r) return t[x];
    push_down(x);
    int mid=l+r>>1;
    if (g<=mid) return query(l,mid,g,x<<1);
    return query(mid+1,r,g,x<<1|1);
}

int main()
{
    freopen("lca.in","r",stdin); freopen("lca.out","w",stdout);
    n=read(); m=read();
    for (int i=1;i<=n;i++) s[i]=read();
    for (int i=1;i<n;i++)
    {
        int x=read(),y=read();
        add(x,y); add(y,x);
    }
    tot=0;
    dfs(1);
    visit[1]=1;
    memset(t,255,sizeof(t));
    while (m--)
    {
        for (c=getchar();c!='Q' && c!='M';c=getchar());
        if (c=='M')
        {
            int x=read();
            if (tu_color[x]) continue;
            tu_color[x]=1;
            change(1,n,dfn[x],dfn[x]+Size[x],s[x],1);
            for (;!visit[x];x=fa[x])
            {
                visit[x]=1;
                if (dfn[fa[x]]<dfn[x]) change(1,n,dfn[fa[x]],dfn[x]-1,s[fa[x]],1);
                if (dfn[x]+Size[x]<dfn[fa[x]]+Size[fa[x]])
                change(1,n,dfn[x]+Size[x]+1,dfn[fa[x]]+Size[fa[x]],s[fa[x]],1);
            }
        }else
        {
            int x=read();
            printf("%d\n",query(1,n,dfn[x],1));
        }
    }
    fclose(stdin); fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值