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

题目大意

n个节点的有根树,根为1,每个节点有黑白两种颜色与一个固定的权值val。一开始全都是白色的,然后给出m个操作。有两种:
M v,把v修改成黑色;
Q v,找一个黑色节点u,使得u,v的lca的权值最大,输出权值,假如没有输出-1。
这里写图片描述

分析

先考虑一下暴力怎么打。
我们可以枚举lca是什么,看看其子树有没有黑色节点。
那么lca一定是v到根上的一个点,枚举lca,并查询lca的子树下,除了包含v的那个子树的其他节点有没有有黑色节点。那么设一个点x为根的子树有sum[x]个黑色节点。只要使得sum[x]-sum[son_with_v]>0,x就是一个合法的lca,可以对答案进行贡献。
那么我们查询和修改就沿着v往上跑到根就行了。这个复杂度是O(m*树高)的,可以过40分。

这样不行啊,怎么办呢,我们令S[x]=sum[dad[x]]-sum[x],那么一个点的S[x]>0,它的贡献就是val[dad[x]]。然后显然发现,S[x]从0变成1的时候才对以后询问有影响,而s[x]是不下降的,所以只要处理得当,我们能够省去很多无用修改。
那么想到这里就很显然用树链剖分维护会很好。
然而比赛的时候只剩40min了,打了个暴力就扫雷去了····

树链剖分很麻烦,又慢又难打又多细节,为了偷懒就换个方向吧。我们考虑一个LCA对子树造成的影响。假如 LCA某棵子树下有个点变成黑色,会发生什么?
1,原本LCA为根的整颗子树都没有白色,现在有个点黑了,整颗子树必须经过LCA到黑点的点到时候查询的时候都能有一个val[LCA],就是除了有黑点的子树,LCA的子树里的其他点全都可以对val[LCA]取max。
2,原本LCA为根的整颗子树有一个黑色的节点了。假如两个黑点的简单路径一定经过LCA的话,LCA的整颗子树都能对val[LCA]取max,这样子,LCA已经对子树内所有点都贡献了,LCA没用了,以后不用管他了。若不经过,那就不管它。
得出一个以点x为根的子树的有用状态:全白,有一个黑点,有多个黑点。
与上面S[x]类似,黑点数量只递增,x改变状态的次数最多2次,那么整棵树状态改变量O(n)。
类似的,我们从v往上跳,注意,假如跳到x时,不能改变dad[x]的状态,那我们就不继续跳了,因为再跳也没有用。然后改变状态的时候用线段树+dfs序维护每个点的最大值,查询直接单点查询就行了。

代码

#include<cstdio>
#include<algorithm>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
typedef long long ll;
const int N=200005;
int first[N],next[N],b[N],sum[N],a[N],dis[N],fa[N],tt,st[N],en[N],son[N],t1;
int tag[N*3];
int x,y,i,j,k,n,m,ans;
char s[20];
void cr(int x,int y)
{
    tt++;
    b[tt]=y;
    next[tt]=first[x];
    first[x]=tt;
}
void dfs(int x,int y)
{
    st[x]=++t1;
    dis[x]=dis[y]+1;
    fa[x]=y;
    sum[x]=0;
    for(int p=first[x];p;p=next[p])
    if (b[p]!=y)
        dfs(b[p],x);
    en[x]=t1;
}
void down(int x,int l,int r)
{
    if (l==r) return;
    tag[x*2]=max(tag[x*2],tag[x]);
    tag[x*2+1]=max(tag[x*2+1],tag[x]);
}
void change(int x,int l,int r,int i,int j,int val)
{
    if (j<i) return;
    if (l==i&&r==j)
    {
        tag[x]=max(tag[x],val);
        down(x,l,r);
        return;
    }
    down(x,l,r);
    int m=(l+r)/2;
    if (m>=j)
        change(x*2,l,m,i,j,val);
    else
    if (m<i)
        change(x*2+1,m+1,r,i,j,val);
    else
    {
        change(x*2,l,m,i,m,val);
        change(x*2+1,m+1,r,m+1,j,val);
    }
}
int get(int x,int l,int r,int pos)
{
    down(x,l,r);
    if (l==r)
        return tag[x];
    int m=(l+r)/2;
    if (m>=pos)
        return get(x*2,l,m,pos);
    else return get(x*2+1,m+1,r,pos);
}
int main()
{
    int size = 512 << 20; // 256MB
    char *p = (char*)malloc(size) + size;
    __asm__("movl %0, %%esp\n" :: "r"(p));

    freopen("lca.in","r",stdin);
    freopen("lca.out","w",stdout);
    scanf("%d %d",&n,&m);
    fo(i,1,n) scanf("%d",a+i);
    fo(i,1,n-1)
    {
        scanf("%d %d\n",&x,&y);
        cr(x,y);
        cr(y,x);
    }
    dfs(1,0);
    fo(i,1,m)
    {
        scanf("%s %d\n",s+1,&x);
        if (s[1]=='M')
        {
            change(1,1,n,st[x],en[x],a[x]);
            while (fa[x])
            {
                if (!son[fa[x]])
                {
                    change(1,1,n,st[fa[x]],st[x]-1,a[fa[x]]);
                    change(1,1,n,en[x]+1,en[fa[x]],a[fa[x]]);
                    son[fa[x]]=x;
                    x=fa[x];
                    continue;
                }
                else if (son[fa[x]]!=x)
                    change(1,1,n,st[son[fa[x]]],en[son[fa[x]]],a[fa[x]]);
                break;
            }
        }else
        {
            ans=get(1,1,n,st[x]);
            if (!ans) ans=-1;
            printf("%d\n",ans);
        }
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值