最近公共祖先 解题报告

最近公共祖先

问题描述

给定一棵\(n\)个节点的有根树,节点编号为\(1\sim n\),其中根节点为\(1\)号节点。每个节点都对应着一种颜色(黑/白)和一个固定的权值,初始时所有节点的颜色都为白色。现在你需要实现以下两种操作:

  • \(\tt{Modify \ v}\):将节点\(v\)的颜色改成黑色;
  • \(\tt{Query \ v}\):找到一个黑色节点\(u\),使得节点\(u\)和\(v\)的最近公共祖先\(z\)对应的权值尽可能大,输出节点\(z\)的权值。如果不存在黑色节点,输出\(-1\)

输入格式

第一行两个正整数\(n,m\),表示节点数和操作数。

第二行\(n\)个正整数\(w_1,w_2,\dots,w_n(w_i\le 10^9)\),表示\(n\)个节点的权值。

接下来\(n-1\)行,每行两个正整数\(a_i,b_i\),表示节点\(a_i\)与节点\(b_i\)之间有一条边相连。

接下来\(m\)行,每行由一个字符串\(str\)和一个正整数\(v\)组成,分别表示操作类型以及操作对应的节点编号。

输出格式

对于每个询问操作,输出一个整数作为这个询问的答案。

说明

对于\(10\%\)的数据:\(n\le 100,m\le 200\)

对于\(20\%\)的数据:\(n\le 3000,m\le 3000\)

对于另外\(20\%\)的数据:保证数据随机生成

对于另外\(20\%\)的数据:保证\(\tt{Query}\)操作在所有\(\tt{Modify}\)操作完成之后

对于\(10\%\)的数据:\(n\le 100000,m\le 200000\)



sb题放\(T3\)了就没做出来(其实还是太菜,和放\(T3\)没得关系

最后一个另\(20\%\)的数据还星,要做个从上到下的树形\(\tt{dp}\),是一个不错的想法。

大概说一下,\(dp_i\)代表\(i\)节点通过\(i\)子树以外的黑点得到的最大\(LCA\)值。

转移
子树\(i\)-子树\(v\)有黑点,\(dp_v=max(dp_i,w_i)\);
否则,\(dp_v=dp_i\)
注意最后自己是黑点还要更新一下子树。

下面正解

题目有个重点,节点只会被染黑而不会被染回去。

如果一个节点黑了,它的每个祖先节点可以对 除了这个点所在儿子的子树外 的自己子树的所有点做出贡献,直接对一个子树-子树的点集算上\(\tt{Ta}\)的贡献就可以了。

一个点被两个来自不同儿子的点算了以后,就没必要管\(\tt{Ta}\)

因此每个点计算贡献的次数是\(O(n)\)的

对子树跑一下\(DFS\)序,线段树维护一下区间修改和单点最大值就可以了。



Code:

#include <cstdio>
#include <cstring>
const int N=1e5+10;
int head[N],to[N<<1],Next[N<<1],cnt;
void add(int u,int v)
{
    to[++cnt]=v,Next[cnt]=head[u],head[u]=cnt;
}
int f[N],dfn[N],low[N],poi[N],dfs_clock,m,n,is[N];
void dfs(int now)
{
    dfn[now]=++dfs_clock;
    for(int i=head[now];i;i=Next[i])
    {
        int v=to[i];
        if(v==f[now]) continue;
        f[v]=now;
        dfs(v);
    }
    low[now]=dfs_clock;
}
#define ls id<<1
#define rs id<<1|1
int max(int x,int y){return x>y?x:y;}
int tag[N<<2];
void change(int id,int L,int R,int l,int r,int d)
{
    if(r<l||!l) return;
    if(L==l&&R==r)
    {
        tag[id]=max(tag[id],d);
        return;
    }
    int Mid=L+R>>1;
    if(r<=Mid) change(ls,L,Mid,l,r,d);
    else if(l>Mid) change(rs,Mid+1,R,l,r,d);
    else change(ls,L,Mid,l,Mid,d),change(rs,Mid+1,R,Mid+1,r,d);
}
int query(int id,int l,int r,int p)
{
    if(l==r) return tag[id];
    int mid=l+r>>1;
    if(p<=mid) return max(tag[id],query(ls,l,mid,p));
    else return max(tag[id],query(rs,mid+1,r,p));
}
int main()
{
    memset(tag,-1,sizeof(tag));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",poi+i);
    for(int u,v,i=1;i<n;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u);
    dfs(1);
    char s[8];is[0]=1;
    for(int las,v,i=1;i<=m;i++)
    {
        scanf("%s%d",s,&v);
        if(s[0]=='Q')
            printf("%d\n",query(1,1,n,dfn[v]));
        else
        {
            change(1,1,n,dfn[v],low[v],poi[v]);
            las=v,is[v]=1,v=f[v];
            do
            {
                change(1,1,n,dfn[v],dfn[las]-1,poi[v]);
                change(1,1,n,low[las]+1,low[v],poi[v]);
                las=v,is[v]=1,v=f[v];
            }while(!is[v]);
            change(1,1,n,dfn[v],dfn[las]-1,poi[v]);
            change(1,1,n,low[las]+1,low[v],poi[v]);
        }
    }
    return 0;
}


2018.10.30

原文地址:https://www.cnblogs.com/ppprseter/p/9876904.html

时间: 2024-11-02 03:08:06

最近公共祖先 解题报告的相关文章

cojs 简单的最近公共祖先 解题报告

我曾经自己想过每考试一次就从考试题中找找idea来出题 这次又找到了一个,先不管原来的考试题是什么 考试题中其中的一部分就是今天的这道题目啦 当时考场上自己比较傻,没有注意到有用的性质,套用了之前黑白树系列的做法 写的是log^2n的,结果导致只能在开O2的情况下A掉这道题目 后来仔细研究了以下,得到了本题的做法 首先我们观察操作中和黑白树系列的那道题目的区别 1.只有染黑操作,没有染白操作 2.不需要可持久化,不需要满足可减性 之后观察题目的性质: 1.转化成暴力写法,每次修改u到根的路径,查

[codevs3160]最长公共子串解题报告|后缀自动机

给出两个由小写字母组成的字符串,求它们的最长公共子串的长度. 样例就觉得不能更眼熟啊...好像之前用后缀数组做过一次 然后发现后缀自动机真的好好写啊...(当然当时学后缀数组的时候也这么认为... 这道题直接把第一个串放到后缀自动机里 第二个串在上面做匹配,但是要注意匹配的时候不能乱搞... 刚开始写了一个类似KMP的东西...想想不对啊 毕竟有些节点的深度是不对的 然而后来发现,我们可以用一个变量tem来保存当前的长度值 如果可以继续匹配,这个值就+1 否则就开始用fail指针不停地退,直到退

[BZOJ2946] [Poi2000]公共串解题报告|后缀数组

给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 单词个数<=5,每个单词长度<=2000 尽管最近在学的是SAM...但是看到这个题还是忍不住想写SA... (其实是不知道应该怎么用SAM做... 对于后缀数组而言,多个字符串的公共子串与两个处理起来并没有什么区别 只要在中间加一些没有用的字符,将多个字符串拼成一个字符串 然后二分答案,对于一个长度L,在一组除了开头其他height都>=L的区间中如果每个字符串的位置都出现过就可以 应该是第二次这么解决一道公共串的题了.. 然

[BZOJ2946][Poi2000]公共串解题报告|后缀自动机

鉴于SAM要简洁一些...于是又写了一遍这题... 不过很好呢又学到了一些新的东西... 这里是用SA做这道题的方法 首先还是和两个字符串的一样,为第一个字符串建SAM 然后每一个字符串再在这个SAM上跑匹配 然而我们最后要的答案是什么呢? 是某个在所有字符串中匹配长度最小值最大的状态子串 然后对于每一个字符串 我们可以记录它在每一个状态子串上的最大匹配长度 最后需要一个非常关键的转移 就是用当前节点的值更新fail指针指向的节点 比如这种情况 如果一次匹配到左边的三个节点,一次匹配到右边的两个

最长公共子序列解题报告

if(x[i]==y[j]) f[i][j]=f[i-1][j-1]+1; else if(x[i]!=y[j]) f[i][j]=max(f[i-1][j], f[i][j-1]); 原决策 这一块其实动规方程是 f[i][j]=max(f[i-1][j-1]+(x[i]==y[j]), f[i][j-1], f[i-1][j]); 动规方程 可以联系到射箭一题 1 #include <stdio.h> 2 #define maxn 501 3 int x[maxn], y[maxn], f

hihoCoder 1062 最近公共祖先&#183;一 最详细的解题报告

题目来源:最近公共祖先·一 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 题目描述 小Ho最近发现了一个神奇的网站!虽然还不够像58同城那样神奇,但这个网站仍然让小Ho乐在其中,但这是为什么呢? “为什么呢?”小Hi如是问道,在他的观察中小Ho已经沉迷这个网站一周之久了,甚至连他心爱的树玩具都弃置一边. “嘿嘿,小Hi,你快过来看!”小Ho招呼道. “你看,在这个对话框里输入我的名字,在另一个对话框里,输入你的名字,再点这个查询按钮,就可以查出来……什么!我们居然有同一

POJ3728 The merchant解题报告

Description There are N cities in a country, and there is one and only one simple path between each pair of cities. A merchant has chosen some paths and wants to earn as much money as possible in each path. When he move along a path, he can choose on

LCA最近公共祖先(POJ1330)

题目链接:http://poj.org/problem?id=1330 解题报告: 先将一个子节点,深搜每一个根节点,并标记. 然后深索另一个子节点,当发现访问过了,就找到了最近的公共祖先. #include <iostream> #include <stdio.h> #include <string.h> using namespace std; const int maxn = 10005; bool vis[maxn]; int father[maxn]; int

P3379 【模板】最近公共祖先(LCA)(倍增LCA)

题目链接:https://www.luogu.org/problem/P3379 题目大意: 给一棵以s为根的无向树,回答m个询问,回答出a和b最近的公共祖先. 解题报告: 倍增LCA的模板题,用一个数组 f [i] [j]表示i结点的第$2^{j}$个祖先.显然,一个点的祖先是f[i][0],对于当前点的第$2^{j}$个祖先的第$2^{j}$个祖先,等于当前点的第$2^{j+1}$个祖先,因为$2^{j}+2^{j}=2^{j+1}$.所以有递推式$f[i][j]=f[f[i][j-1]][