[Luogu] LCA

https://www.luogu.org/problemnew/show/P4211

baoli

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;
const int N = 5e4 + 10;
const int Mod = 201314;

#define gc getchar()

#define RR freopen("gg.in", "r", stdin) 

int fa[N] = {-1}, deep[N], cnt[N], head[N];
struct Node {int u, v, nxt;} G[N];
int n, Ty, now = 1;

inline int read() {
    int x = 0; char c = gc;
    while(c < ‘0‘ || c > ‘9‘) c = gc;
    while(c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - ‘0‘, c = gc;
    return x;
}

inline void Add(int u, int v) {
    G[now].u = u; G[now].v = v; G[now].nxt = head[u]; head[u] = now ++;
}

void Dfs(int u, int dep) {
    deep[u] = dep;
    for(int i = head[u]; ~ i; i = G[i].nxt) {
        int v = G[i].v;
        if(v != fa[u]) Dfs(v, dep + 1);
    }
}

void Dfs_2(int u) {
    if(head[u] == -1) return ;
    for(int i = head[u]; ~ i; i = G[i].nxt) {
        int v = G[i].v;
        if(v != fa[u]) {
            Dfs_2(v);
            cnt[u] += cnt[v];
        }
    }
}

int main() {
    n = read();
    Ty = read();
    for(int i = 0; i < n; i ++) head[i] = -1;
    for(int i = 1; i < n; i ++) {
        int u = read(); fa[i] = u;
        Add(u, i); Add(i, u);
    }
    Dfs(0, 1);
    while(Ty --) {
        int l = read(); int r = read(); int z = read();
        for(int i = l; i <= r; i ++) cnt[i] ++;
        Dfs_2(0);
        int imp = z;
        int Answer = 0;
        while(fa[imp] != -1) {
            Answer += cnt[imp];
            Answer %= Mod;
            imp = fa[imp];
        } Answer += cnt[imp];
        Answer %= Mod;
        cout << Answer << endl;
        memset(cnt, 0, sizeof cnt);
    }
    return 0;
} 

考虑这样的等价问题,如果我们把一个点 x 到 Root 的路径上每个点的权值赋为 1 ,其余点的权值为 0,那么从 LCA(x, y) 的 Depth 就是从 y 到 Root 的路径上的点权和。

这个方法是可以叠加的,这是非常有用的一点。如果我们把 [l, r] 的每个点到 Root 的路径上所有点的权值 +1,再求出从 c 到 Root 的路径点权和,即为 [l, r] 中所有点与 c 的 LCA 的 Depth 和。

不仅满足可加性,还满足可减性,这就更好了!

那么我们就可以对每个询问 [l, r] 做一个差分,用 Query(r) - Query(l - 1) 作为答案。这样就有一种离线算法:将 n 个点依次操作,将其到 Root 的路径上的点权值 +1 ,然后如果这个点是某个询问的 l - 1 或 r ,就用那个询问的 c 求一下到 Root 的路径和,算入答案中。

#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 50005
#define M 201314
#define K 150005
using namespace std;
struct graph {
    int nxt,to;
} e[N];
struct quest {
    int x,z,n,ans;
} l[N],r[N];
struct linetree {
    int l,r,s,len,lzy;
} lt[K];
int g[N],n,q,t1,t2,cnt;
int f[N],p[N],dep[N],top[N],siz[N],son[N];
inline int read() {
    int ret=0;
    char c=getchar();
    while(!isdigit(c))
        c=getchar();
    while(isdigit(c)) {
        ret=(ret<<1)+(ret<<3)+c-‘0‘;
        c=getchar();
    }
    return ret;
}
inline void addedge(int x,int y) {
    e[++cnt].nxt=g[x];
    g[x]=cnt;
    e[cnt].to=y;
}
inline void dfs1(int u) {
    int m=0;
    siz[u]=1;
    for(int i=g[u]; i; i=e[i].nxt) {
        f[e[i].to]=u;
        dep[e[i].to]=dep[u]+1;
        dfs1(e[i].to);
        siz[u]+=siz[e[i].to];
        if(siz[e[i].to]>m) {
            son[u]=e[i].to;
            m=siz[e[i].to];
        }
    }
}
inline void dfs2(int u,int tp) {
    top[u]=tp;
    p[u]=++cnt;
    if(son[u]) dfs2(son[u],tp);
    for(int i=g[u]; i; i=e[i].nxt)
        if(e[i].to!=son[u])
            dfs2(e[i].to,e[i].to);
}
inline void build(int u,int l,int r) {
    lt[u].l=l;
    lt[u].r=r;
    lt[u].len=lt[u].r-lt[u].l+1;
    if(lt[u].l<lt[u].r) {
        int lef=u<<1,rig=u<<1|1;
        int mid=lt[u].l+lt[u].r>>1;
        build(lef,l,mid);
        build(rig,mid+1,r);
    }
}
inline int cover(int u,int l,int r) {
    if(lt[u].l>=l&&lt[u].r<=r) {
        ++lt[u].lzy;
        lt[u].s=(lt[u].s+lt[u].len)%M;
    } else if(lt[u].l<lt[u].r) {
        int lef=u<<1,rig=u<<1|1;
        int mid=lt[u].l+lt[u].r>>1;
        if(lt[u].lzy) {
            lt[lef].lzy+=lt[u].lzy;
            lt[rig].lzy+=lt[u].lzy;
            lt[lef].s=(lt[lef].s+lt[lef].len*lt[u].lzy)%M;
            lt[rig].s=(lt[rig].s+lt[rig].len*lt[u].lzy)%M;
            lt[u].lzy=0;
        }
        if(l<=mid) cover(lef,l,r);
        if(r>mid) cover(rig,l,r);
        lt[u].s=(lt[lef].s+lt[rig].s)%M;
    }
}
inline int ask(int u,int l,int r) {
    if(lt[u].l>=l&&lt[u].r<=r)
        return lt[u].s;
    if(lt[u].l<lt[u].r) {
        int lef=u<<1,rig=u<<1|1,ret=0;
        int mid=lt[u].l+lt[u].r>>1;
        if(lt[u].lzy) {
            lt[lef].lzy+=lt[u].lzy;
            lt[rig].lzy+=lt[u].lzy;
            lt[lef].s=(lt[lef].s+lt[lef].len*lt[u].lzy)%M;
            lt[rig].s=(lt[rig].s+lt[rig].len*lt[u].lzy)%M;
            lt[u].lzy=0;
        }
        if(l<=mid) ret=(ret+ask(lef,l,r))%M;
        if(r>mid) ret=(ret+ask(rig,l,r))%M;
        return ret;
    }
}
inline void add(int x,int y) {
    int t;
    while(top[x]!=top[y]) {
        if(dep[top[x]]<dep[top[y]]) {
            t=x;
            x=y;
            y=t;
        }
        cover(1,p[top[x]],p[x]);
        x=f[top[x]];
    }
    if(p[x]>p[y]) {
        t=x;
        x=y;
        y=t;
    }
    cover(1,p[x],p[y]);
}
inline int que(int x,int y) {
    int ret=0,t;
    while(top[x]!=top[y]) {
        if(dep[top[x]]<dep[top[y]]) {
            t=x;
            x=y;
            y=t;
        }
        ret=(ret+ask(1,p[top[x]],p[x]))%M;
        x=f[top[x]];
    }
    if(p[x]>p[y]) {
        t=x;
        x=y;
        y=t;
    }
    ret=(ret+ask(1,p[x],p[y]))%M;
    return ret;
}
inline bool cmp1(quest x,quest y) {
    return x.x<y.x;
}

inline bool cmp2(quest x,quest y) {
    return x.n<y.n;
}
inline void Aireen() {
    n=read();
    q=read();
    for(int i=2,j; i<=n; ++i) {
        j=read()+1;
        addedge(j,i);
    }
    for(int i=1; i<=q; ++i) {
        l[i].n=r[i].n=i;
        l[i].x=read();
        r[i].x=read()+1;
        l[i].z=r[i].z=read()+1;
    }
    sort(l+1,l+1+q,cmp1);
    sort(r+1,r+1+q,cmp1);
    while(t1<=q&&!l[t1].x) ++t1;
    while(t2<=q&&!r[t2].x) ++t2;
    dep[1]=1;
    dfs1(1);
    cnt=0;
    dfs2(1,1);
    build(1,1,n);
    for(int i=1; i<=n; ++i) {
        add(1,i);
        while(t1<=q&&l[t1].x==i) {
            l[t1].ans=que(1,l[t1].z);
            ++t1;
        }
        while(t2<=q&&r[t2].x==i) {
            r[t2].ans=que(1,r[t2].z);
            ++t2;
        }
    }
    sort(l+1,l+1+q,cmp2);
    sort(r+1,r+1+q,cmp2);
    for(int i=1; i<=q; ++i)
        printf("%d\n",(r[i].ans-l[i].ans+M)%M);
}
int main() {
    Aireen();
    fclose(stdin);
    fclose(stdout);
    return 0;
}

原文地址:https://www.cnblogs.com/shandongs1/p/8455762.html

时间: 2024-10-30 23:26:59

[Luogu] LCA的相关文章

【OI】倍增求LCA

╭(′▽`)╯ 总之,我们都知道lca是啥,不需要任何基础也能想出来怎么用最暴力的方法求LCA,也就是深度深的点先跳到深度浅的点的同一深度,然后一起向上一步步跳.这样显然太慢了! 所以我们要用倍增,倍增比较屌,直接2^k速度往上跳,而且复杂度和树剖lca差不多,那么步骤分为两步 1.让两个点到同一深度 2.到了同一深度同步往上跳 反正我一开始看的时候一直在想,万一跳过了怎么办?哈哈哈,所以说我们有办法嘛: 定义deepv为v点的深度,设两个要求lca的点分别为a,b,且deepa >= deep

[luogu]P1600 天天爱跑步[LCA]

[luogu]P1600 [NOIP 2016]天天爱跑步 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.«天天爱跑步»是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵包含n个结点和n−1条边的树, 每条边连接两个结点,且任意两个结点存在一条路径互相可达.树上结点编号为从1到n的连续正整数. 现在有m个玩家,第i个玩家的起点为Si?,终点为Ti? .每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速

【Luogu】P1967货车运输(最大生成森林+倍增LCA)

题目链接 倍增LCA是个什么蛇皮原理啊,循环完了还得再往上跳一次才能到最近公共祖先 合着我昨天WA两次就是因为这个 建最大生成森林,因为图不一定是联通的,所以不一定是一棵树.这个地方用克鲁斯卡尔就好了 然后给这个森林跑一遍DFS,顺便倍增 然后对于每个询问跑LCA,倍增的时候已经顺便求出了最小边权,所以往上跳的同时更新答案. 代码如下 #include<cstdio> #include<cctype> #include<cstdlib> #include<cmat

【原创】洛谷 LUOGU P3379 【模板】最近公共祖先(LCA) -&gt; 倍增

P3379 [模板]最近公共祖先(LCA) 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询问的结果. 输入输出样例 输入

luogu 1967 货车运输(最大瓶颈生成树+LCA)

题意:给出一颗n个点的图,q个询问,每次询问u到v的路径中最小的边最大是多少. 图的最大瓶颈生成树有一个性质,对于该图的任意两个点,在树中他们之间路径的最小边最大. 由于这个图不一定联通,于是我们对它的联通块都求一次最大瓶颈生成树. 每次询问就变成了在最大瓶颈生成树上找出u到v路径的最小边. 这个显然可以用LCA维护点到它的2^x祖先之间的边的最小值来解决. # include <cstdio> # include <cstring> # include <cstdlib&g

Luogu P3320 [SDOI2015]寻宝游戏 / 异象石 【LCA/set】

期末考试结束祭! 在期末考试前最后一发的测试中,异象石作为第二道题目出现QAQ.虽然知道是LCA图论,但还是敲不出来QAQ. 花了两天竞赛课的时间搞懂(逃 异象石(stone.pas/c/cpp)题目描述Adera 是 Microsoft 应用商店中的一款解谜游戏.异象石是进入 Adera 中异时空的引导物,在 Adera 的异时空中有一张地图.这张地图上有 N 个点,有 N-1 条双向边把它们连通起来.起初地图上没有任何异象石,在接下来的 M个时刻中,每个时刻会发生以下三种类型的事件之一:1.

【题解】Luogu SP8791 DYNALCA - Dynamic LCA

原题传送门 这题用Link-Cut-Tree解决,Link-Cut-Tree详解 这道题的难点就在如何求LCA: 我们珂以先对其中一个点进行access操作,然后对另一个点进行access操作,因为LCA到根的边一定都由第一次access变为实边了,在之后的这一次access操作的最后一条从虚边变为实边的边的父亲就是两点的LCA 回求LCA后,剩下的几乎是模板,但有两种做法 1.建立虚拟点n+1,保证一直是一颗有n+1个点的树,写起来比较无脑(lct部分珂以复制),常数比较大 #include

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

qwq 懒得写了明天补 为啥不开两倍会re啊 代码 #include<cstdio> #include<iostream> #include<cmath> #define MogeKo qwq using namespace std; const int maxn = 500005*2; int head[maxn],to[maxn],nxt[maxn],dpth[maxn]; int n,m,s,u,v,cnt,p[maxn][30]; void add(int x,

[树上差分][lca] Luogu P3258 松鼠的新家

题目描述 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的.天哪,他居然真的住在”树“上. 松鼠想邀请小熊维尼前来参观,并且还指定一份参观指南,他希望维尼能够按照他的指南顺序,先去a1,再去a2,......,最后到an,去参观新家.可是这样会导致维尼重复走很多房间,懒惰的维尼不停地推辞.可是松鼠告诉他,每走到一个房间,他就可以从房间拿一块糖果吃. 维尼是个馋家伙,立马就答应了.现在松鼠希望知道为了保证维尼有