暑假集训 || LCA && RMQ

LCA定义为对于一颗树 树上两个点的最近公共祖先

一.Tarjan求LCA(离线方法

https://blog.csdn.net/lw277232240/article/details/77017517

二.倍增法求LCA

void dfs(int u, int f)
{
    for(int i = 1; i <= 18; i++)
        if(deep[u] >= (1<<i))
            fa[u][i] = fa[fa[u][i-1]][i-1];
    for(int i = head[u];i;i = nxt[i])
    {
        int v = l[i].t;
        if(v != f)
        {
            deep[v] = deep[u] + 1;
            dist[v] = dist[u] + l[i].d;
            fa[v][0] = u;
            dfs(v, u);
        }
    }
}
int lca(int x, int y)
{
    if(deep[x] < deep[y])
        swap(x, y);
    int delta = deep[x] - deep[y];
    for(int i = 0; i <= 18; i++)
        if((1<<i) & delta)
            x = fa[x][i];
    for(int i = 18; i >= 0; i--)
        if(fa[x][i] != fa[y][i])
        {
            x = fa[x][i];
            y = fa[y][i];
        }
    if(x == y) return x;
    else return fa[x][0];
}
LL getdis(int x, int y)
{
    int z = lca(x, y);
    return dist[x] + dist[y] - 2 * dist[z];
}

可以用来求一棵树上两点之间的最短距离

例题:

Gym 101808K 思路题

题意:给一个有n个点,n条边的图,n为1e5,查询任两点间的最短距离

思路:n个点n-1条边的话就是树,这个图就比树多了一条边,把这条边拿出来考虑

任意两点间的最短路有两种情况,一是经过这条边,二是不经过

建图的时候不加这一条边

x和y的距离 经过这条边的话x->uu + ww + vv->y或x->vv + ww + uu->y

不经过的话直接求即可

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long LL;
const int SZ = 200010;
const int INF = 1e9+10;
int head[SZ], nxt[SZ], tot = 0, deep[SZ], fa[SZ][20];
int fab[SZ];
LL dist[SZ];
struct node
{
    int t, d;
}l[SZ];
void build(int f, int t, int d)
{
    l[++tot].t = t;
    l[tot].d = d;
    nxt[tot] = head[f];
    head[f] = tot;
}
int n;
void dfs(int u, int f)
{
    for(int i = 1; i <= 18; i++)
        if(deep[u] >= (1<<i))
            fa[u][i] = fa[fa[u][i-1]][i-1];
    for(int i = head[u];i;i = nxt[i])
    {
        int v = l[i].t;
        if(v != f)
        {
            deep[v] = deep[u] + 1;
            dist[v] = dist[u] + l[i].d;
            fa[v][0] = u;
            dfs(v, u);
        }
    }
}
int lca(int x, int y)
{
    if(deep[x] < deep[y])
        swap(x, y);
    int delta = deep[x] - deep[y];
    for(int i = 0; i <= 18; i++)
        if((1<<i) & delta)
            x = fa[x][i];
    for(int i = 18; i >= 0; i--)
        if(fa[x][i] != fa[y][i])
        {
            x = fa[x][i];
            y = fa[y][i];
        }
    if(x == y) return x;
    else return fa[x][0];
}
LL getdis(int x, int y)
{
    int z = lca(x, y);
    return dist[x] + dist[y] - 2 * dist[z];
}
void init()
{
    memset(head, 0, sizeof(head));
    tot = 0;
    memset(deep, 0, sizeof(deep));
    memset(fa, 0, sizeof(fa));
    for(int i = 1; i <= n; i++) fab[i] = i;
}
int find(int x)
{
    return x == fab[x] ? x : fab[x] = find(fab[x]);
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        int q;
        scanf("%d %d", &n, &q);
        init();
        int uu, vv, ww;
        for(int i = 0; i < n; i++)
        {
            int u, v, w;
            scanf("%d %d %d", &u, &v, &w);
            int fu = find(u), fv = find(v);
            if(fu == fv) uu = u, vv = v, ww = w;
            else
            {
                fab[fu] = fv;
                build(u, v, w);
                build(v, u, w);
            }
        }
        dist[1] = 0;
        dfs(1, 0);
        //printf("%d %d\n", uu, vv);
        //for(int i = 1; i <= n; i++) printf("%lld ", dist[i]);
        while(q--)
        {
            int x, y;
            scanf("%d %d", &x, &y);
            LL dis1 = getdis(x, y);
            LL dis2 = min(getdis(x, uu) + ww + getdis(y, vv), getdis(x, vv) + ww + getdis(y, uu));
            printf("%lld\n", min(dis1, dis2));
        }
    }
    return 0;
}

Gym 101810M

题意:一棵树,每条边来回都可以获得不同的值,每条边只能去一次回一次,任意查询从s到t最多能获得多少值

思路:发现能获得的值是整棵树上的权值 - 从t走最短路到s获得的值

用dist[0][u]记录从根走到u总的值

用dist[1][u]记录从u走到根总的值

画个图推个式子就ok了

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long LL;
const int SZ = 400010;
const int INF = 1e9+10;
int head[SZ], nxt[SZ], tot = 0, deep[SZ], fa[SZ][20];
int dist[2][SZ];
struct node
{
    int t, c1, c2;
}l[SZ];
void build(int f, int t, int c1, int c2)
{
    l[++tot].t = t;
    l[tot].c1 = c1;
    l[tot].c2 = c2;
    nxt[tot] = head[f];
    head[f] = tot;
}
int n;
void dfs(int u, int f)
{
    for(int i = 1; i <= 18; i++)
        if(deep[u] >= (1<<i))
            fa[u][i] = fa[fa[u][i-1]][i-1];
    for(int i = head[u];i;i = nxt[i])
    {
        int v = l[i].t;
        if(v != f)
        {
            deep[v] = deep[u] + 1;
            dist[0][v] = dist[0][u] + l[i].c1;
            dist[1][v] = dist[1][u] + l[i].c2;
            fa[v][0] = u;
            dfs(v, u);
        }
    }
}
int lca(int x, int y)
{
    if(deep[x] < deep[y])
        swap(x, y);
    int delta = deep[x] - deep[y];
    for(int i = 0; i <= 18; i++)
        if((1<<i) & delta)
            x = fa[x][i];
    for(int i = 18; i >= 0; i--)
        if(fa[x][i] != fa[y][i])
        {
            x = fa[x][i];
            y = fa[y][i];
        }
    if(x == y) return x;
    else return fa[x][0];
}
void init()
{
    memset(head, 0, sizeof(head));
    tot = 0;
    memset(deep, 0, sizeof(deep));
    memset(fa, 0, sizeof(fa));
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        init();
        int sum = 0;
        for(int i = 0; i < n-1; i++)
        {
            int u, v, w1, w2;
            scanf("%d %d %d %d", &u, &v, &w1, &w2);
            build(u, v, w1, w2);
            build(v, u, w2, w1);
            sum = sum + w1 + w2;
        }
        dist[0][1] = 0, dist[1][1] = 0;
        dfs(1, 0);
        int q;
        scanf("%d", &q);
        while(q--)
        {
            int x, y;
            scanf("%d %d", &x, &y);
            int z = lca(x, y);
            int ans = dist[1][y] - dist[1][z] + dist[0][x] - dist[0][z];
            printf("%d\n", sum - ans);
        }
    }
    return 0;
}

RMQ:区间最值查询问题

用f[i][j]表示 从a[i] 开始 往后2^j个数里面的 最大/最小 值或GCD

void st(int n)
{
    for(int i = 1; i <= n; i++)
        f[i][0] = a[i];
    for(int j = 1; (1 << j) <= n; j++)
        for(int i = 1; i + (1 << j) - 1 <= n; i++)
            f[i][j] = max(f[i][j-1], f[i + (1<<(j-1))][j-1]);
}
int RMQ(int l, int r)
{
    int k = 0;
    while((1<<(k + 1) <= r - l + 1)) k++;
    return max(f[l][k], f[r - (1<<k) + 1][k]);
}

最小值把max改成min, GCD把max改成__gcd

例题:

POJ 2019 二维RMQ

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
const int SZ = 550;
const int INF = 1e9+10;
int a[SZ][SZ], mmax[SZ][SZ][15], mmin[SZ][SZ][15];
void st(int n)
{
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            mmax[i][j][0] = mmin[i][j][0] = a[i][j];
    for(int j = 1; (1 << j) <= n; j++)
        for(int i = 1; i + (1 << j) - 1 <= n; i++)
            for(int k = 1; k <= n; k++)
            {
                mmax[k][i][j] = max(mmax[k][i][j-1], mmax[k][i + (1<<(j-1))][j-1]);
                mmin[k][i][j] = min(mmin[k][i][j-1], mmin[k][i + (1<<(j-1))][j-1]);
            }
}
int RMQ(int x, int l, int r, int b)
{
    int k = 0;
    while((1<<(k + 1) <= r - l + 1)) k++;
    int ans_max = -INF, ans_min = INF;
    for(int i = x; i < x + b; i++)
    {
        ans_max = max(ans_max, max(mmax[i][l][k], mmax[i][r- (1<<k) + 1][k]));
        ans_min = min(ans_min, min(mmin[i][l][k], mmin[i][r- (1<<k) + 1][k]));
    }
    return (ans_max - ans_min);
}
int main()
{
    int n, b, k;
    scanf("%d %d %d", &n, &b, &k);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            scanf("%d", &a[i][j]);
    st(n);
    for(int i = 0; i < k; i++)
    {
        int x, y;
        scanf("%d %d", &x, &y);
        int l = y, r = y + b - 1;
        printf("%d\n", RMQ(x, l, r, b));
    }
    return 0;
}

HDU 5726

题意:给一段序列,任意查询一个区间内所有数的GCD,以及有多少个区间的GCD数和它相同

思路:RMQ+二分。。考场上有思路了但是没敢敲QAQ

发现序列越长,GCD是不增的,于是对于每个数可以通过二分判断它往后多少个数,这一段里面拥有相同的GCD

用map记录即可

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <map>
using namespace std;
typedef long long LL;
const int SZ = 100010;
const int INF = 1e9+10;
int a[SZ];
int f[SZ][22];
map<int, LL> mp;
void st(int n)
{
    for(int i = 1; i <= n; i++)
        f[i][0] = a[i];
    for(int j = 1; (1 << j) <= n; j++)
        for(int i = 1; i + (1 << j) - 1 <= n; i++)
        {
            f[i][j] = __gcd(f[i][j-1], f[i + (1<<(j-1))][j-1]);
        }
}
int RMQ(int l, int r)
{
    int k = 0;
    while((1<<(k + 1) <= r - l + 1)) k++;
    return __gcd(f[l][k], f[r - (1<<k) + 1][k]);
}
int main()
{
    int T, tt = 0;
    scanf("%d", &T);
    while(T--)
    {
        int n;
        scanf("%d", &n);
        mp.clear();
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= 18; j++)
                f[i][j] = 1;
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        st(n);
        for(int i = 1; i <= n; i++)
        {
            int k = i;
            while(k <= n)
            {
                int l = k, r = n;
                while(l <= r)
                {
                    int mid = (l + r + 1) >> 1;
                    if(RMQ(i, mid) < RMQ(i, k)) r = mid - 1;
                    else l = mid + 1;
                }
                mp[RMQ(i, k)] += LL(l - k);
                k = l;
            }
        }
        int q;
        scanf("%d", &q);
        printf("Case #%d:\n", ++tt);
        while(q--)
        {
            int x, y;
            scanf("%d %d", &x, &y);
            int ans = RMQ(x, y);
            printf("%d %lld\n", ans, mp[ans]);
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/pinkglightning/p/9520801.html

时间: 2024-11-06 23:26:07

暑假集训 || LCA && RMQ的相关文章

2017暑假集训前总结和规划

距离大一进来已经一年了啊,感觉还是啥也不会,哎,太差了,总结一下这一年都学了写什么吧! 大一寒假开始专题,刷过的有:dp,dfs和bfs,数论(gcd拓展gcd,欧拉定理等等,但是中国剩余定理没学,等复习的时候再学吧),并查集,最短路(bellman-fprd,dijkstra,floyd-warshall,spfa),最小生成树(prim,kruskal),线段树,二分三分 大一下学期有:拓扑排序,基础计算几何(直线线段相交,快速排除实验,跨立实验),矩阵快速幂,博弈基础(nim博弈,威佐夫博

暑假集训0815

最近打了两场多校和一场中期比赛 中期比赛的话也就那么回事= =水题集合(从各种意义上 两场多校被各种虐,已经弱到谁都可以上(rbq)的程度了TOT 团队配合还要慢慢磨合,毕竟刚刚组队不久 这两次比赛有好几题都是差一点就想出来,毕竟还是图样 继续加油(shui)吧 还有就是一定要好好读题啊!最好读完题之后就手算一下数据,以防理解错题意.如果理解错了不会做就罢了,像中期比赛的时候读完题愉悦的打完代码才发现理解错题意了简直不能忍(论坑队友的正确姿势) Multi-University Training

UESTC 912 树上的距离 --LCA+RMQ+树状数组

1.易知,树上两点的距离dis[u][v] = D[u]+D[v]-2*D[lca(u,v)] (D为节点到根节点的距离) 2.某条边<u,v>权值一旦改变,将会影响所有以v为根的子树上的节点到根节点的距离,很明显,DFS一遍后以v为根的子树在DFS序列中是连续的一段,及转化为区间更新问题,可以用树状数组. 做法:先把求LCA解决,LCA可以转化为RMQ问题,可参见:LCA转RMQ, 即转化为LCA(T,u,v) = RMQ(B,pos[u],pos[v]),其中B为深度序列.预先DFS可以处

暑假集训7.11 字符串回文暴力

#include #include #include #include #include #include #include #include #include #include #include using namespace std; typedef long long ll; typedef unsigned long long ull; #define MM(a,b) memset(a,b,sizeof(a)); const double eps = 1e-10; const int i

暑假集训0808

来HIT参加暑假集训也有将近一周了,一直什么都没写= =. 记一下今天的比赛吧,以后争取每天更新一篇总结. 我是弱比.只能出6题. A:POJ1417 很容易发现yes表示两个人是同一类,no是不同类,然后怎么判断方案是否唯一我就不会了...我是有多么的弱...类似于背包DP,就是用前i个大类填满j个人数的方案. B:HDU1711 裸KMP C:POJ 2503 一个字典的实现.很显然的Trie.(我就想说出题人就不能给个n么..读入麻烦死..) D:POJ2828  比赛的时候还是想的有问题

2017暑假集训~心路历程

2017年暑假集训的个人赛阶段已经结束了. 回想这7场比赛,前几场发挥的还可以. 但是第4场之后开始状态开始下滑.第4场是在TOJ上做的,题数马马虎虎,就是罚时有点多. 第5场的FOJ的比赛打得十分糟糕.因为并不熟悉这个OJ, 赛前测试工作也没有作足. 比赛的时候各种意想不到的CE,WA.这样一来我前两小时只过了一题. 不过最后两小时至少翻到了不至于垫底的位置…… 第6场发挥也不好,被一道树形DP搞住了(这应该是我的强项),卡了一个半小时发现自己题目读错了.结果还是想不出来……然后另一道贪心因为

暑假集训(2)第五弹 ----- Who&#39;s in the Middle(poj2388)

G - Who's in the Middle Crawling in process... Crawling failed Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Description FJ is surveying his herd to find the most average cow. He wants to know how much milk this 'median

暑假集训练习赛题解

比赛链接:http://acm.nyist.net/JudgeOnline/problemset.php?cid=205 对于第一道题 UFS(Union  Find  Set) ,请参见http://blog.csdn.net/u011632342/article/details/37814289,题目大意一样,解法一样,不过后台测试数据还没整太多,数据比较弱... 对于第二道题STR(STRing),本来是想着给大家"送福利"呢,可能由于我的题目表述能力不太好或者样例数据的特殊性或

ACM暑假集训总结

暑假集训总结 回想去年暑假集训,和boblee.yyd组队时,他们为了复习考研,只有我一人默默的在基地训练,再到今年和yj.cq组队,三人能在基地一起刷题训练.再回想去年暑假自己作为新人,只能抱着两位队友的大腿,到今年作为一队的成员,要挑起集训队的大梁.从去年的全俱乐部开黑只能压线过几场网络赛,再到今年的队伍间各自做题,照样能没什么压力的通过网赛.亲眼见证俱乐部由弱到强,由不为人知到众所周知,自己不仅感触颇多,也同样敬佩起当初办起这个俱乐部的创始人们. 作为一个刚刚接触OI就要面临高考的人,才刚