【bzoj4551】【NOIP2016模拟7.11】树

12 篇文章 0 订阅
5 篇文章 0 订阅

题目

在2016年,佳媛姐姐刚刚学习了树,非常开心。现在他想解决这样一个问题:给定一颗有根树(根为1),有以下
两种操作:1. 标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均无标记,而且对于某个
结点,可以打多次标记。)2. 询问操作:询问某个结点最近的一个打了标记的祖先(这个结点本身也算自己的祖
先)你能帮帮他吗?

分析

此题有很多种方法

暴力+并查集

转了别人的
考虑离线处理。
我们将所有的操作倒序处理,利用并查集进行维护。对于一个修改操
作(即失去标记),相当于将这一联通块块与它父亲的联通块合并。对于
一个查询操作,直接查询所在的块的父亲即可。
实现中有些细节需要注意,比如每个点可能会被标记多次,所以要以
最早的被标记时间做为“失去标记”的时间。
时间复杂度为O(n + qα(n))

DFN序+线段树

首先搞一遍dfn序,
接着发现,在用一棵子树中,dfn序都是相邻的。
好了,线段树修改查询。

树链剖分

单独修改、查询1到num的路径。
裸题一枚。

#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
const int maxlongint=2147483647;
const int mo=1000000007;
const int N=100005;
using namespace std;
int tree[N*6],top[N],d[N],size[N],deep[N],son[N],fa[N],n,m,tot,ans;
int last[N*2],next[N*2],to[N*2],bef[N],aft[N];
void bj(int x,int y)
{
    next[++tot]=last[x];
    last[x]=tot;
    to[tot]=y;
}
void dg(int x,int y)
{
    size[x]=1;
    int mx=0;
    for(int i=last[x];i;i=next[i])
    {
        int j=to[i];
        if(j!=y)
        {
            deep[j]=deep[x]+1;
            fa[j]=x;
            dg(j,x);
            size[x]+=size[j];
            if(size[j]>mx)
            {
                mx=size[j];
                son[x]=j;
            }
        }
    }
}
int dg1(int x,int y)
{
    d[++tot]=x;
    aft[x]=tot;
    bef[tot]=x;
    if(!top[x])
        top[x]=x;
    if(son[x])
    {
        top[son[x]]=top[x];
        dg1(son[x],x);
    }
    for(int i=last[x];i;i=next[i])
    {
        int j=to[i];
        if(j!=y && j!=son[x])
        {
            dg1(j,x);
        }
    }
}
int put(int v,int l,int r,int x)
{
    if(l==r)
    {
        tree[v]=bef[x];
        return 0;
    }
    int mid=(l+r)/2;
    if(x<=mid)
    {
        put(v*2,l,mid,x);
    }
    else
        put(v*2+1,mid+1,r,x);
    tree[v]=deep[tree[v*2]]>deep[tree[v*2+1]]?tree[v*2]:tree[v*2+1];
}
int find(int v,int l,int r,int x,int y)
{
    if(l==x && r==y)
    {
        ans=deep[ans]>deep[tree[v]]?ans:tree[v];
        return 0;
    }
    int mid=(l+r)/2;
    if(y<=mid)
    {
        find(v*2,l,mid,x,y);
    }
    else
    if(mid<x)
    {
        find(v*2+1,mid+1,r,x,y);
    }
    else
    {
        find(v*2,l,mid,x,mid);
        find(v*2+1,mid+1,r,mid+1,y);
    }
    tree[v]=deep[tree[v*2]]>deep[tree[v*2+1]]?tree[v*2]:tree[v*2+1];
}
int main()
{
    freopen("4604.in","r",stdin);
    freopen("4604.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n-1;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        bj(x,y);
        bj(y,x);
    }
    deep[1]=1;
    dg(1,0);
    top[1]=1;
    tot=0;
    dg1(1,0);
    put(1,1,n,aft[1]);
    scanf("\n");
    for(int i=1;i<=m;i++)
    {
        int c;
        int x,y;
        c=getchar();
        while((c!='Q')&&(c!='C'))c=getchar();
        scanf("%d\n",&x);
        if(c=='C')
        {
            put(1,1,n,aft[x]);
        }
        else
        {
            ans=0;
            while(top[x]!=top[1])
            {
                find(1,1,n,aft[top[x]],aft[x]);
                x=fa[top[x]];
            }
            find(1,1,n,aft[1],aft[x]);
            printf("%d\n",ans);
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值