乐师理工ACM集训 - LCA

HDU2586 How far away ?

传送门:HDU2586 How far away ?

题目大意

  T个测试样例,每个测试样例给定一个N(N间房屋)和M(M个查询),接下来N-1行会给出三个数i,j,k,表示房间 i 到房间 j 的距离为 k。再接下来M行分别会给出两个数,表示需要查询的两个房屋。(T<=10,2<=n<=40000,1<=m<=200,0<k<=40000)
  道路的建造方式是每两栋房屋之间都有一条唯一的简单路径(“简单”意味着您不能两次访问某个地方)
  问:对于M个询问,每个询问的两个房屋之间的距离为多少?

解题思路

  求树上两点距离可用LCA求解。

distance(u,v) = dis[u] + dis[v] - 2 * dis[LCA(u,v)]
其中 distance 是树上两点间的距离,dis 代表某点到树根的距离。

AC代码【倍增 + LCA】

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 4e4 + 5;
const int MAXM = 4e4 + 5;
const int INF = 0x3f3f3f3f;
int n,m,node,N;
int head[MAXN],dp[MAXN][30],deep[MAXN],dis[MAXN];
struct Node {
    int to,w,pre;
}edge[MAXM << 1];
void init() {
    memset(head, -1, sizeof(head));
    memset(dp, 0, sizeof(dp));
    memset(dis, 0, sizeof(dis));
    memset(deep, 0, sizeof(deep));
    node = 0;
    N = (int)log2(n);
}
void add_edge(int u, int v, int w) {
    edge[node].to = v;
    edge[node].w = w;
    edge[node].pre = head[u];
    head[u] = node++;
}
void dfs(int now, int fa) {
    dp[now][0] = fa;
    for (int i = 1; (1 << i) <= deep[now]; i++) {
        dp[now][i] = dp[dp[now][i - 1]][i - 1];
    }
    for (int i = head[now]; ~i; i = edge[i].pre) {
        int v = edge[i].to;
        if (v != fa) {
            deep[v] = deep[now] + 1;
            dis[v] = dis[now] + edge[i].w;
            dfs(v,now);
        }
    }
}
int lca(int u, int v) {
    if (deep[u] < deep[v]) {
        swap(u,v);
    }
    int h = deep[u] - deep[v];
    for (int i = 0, j = (1 << i); j <= h; i++, j  = (1 << i)) {
        if (h & j) {
            u = dp[u][i];
        }
    }
    if (u == v) {
        return u;
    }
    for (int i = N; i >= 0; i--) {
        if (dp[u][i] != dp[v][i]) {
            u = dp[u][i];
            v = dp[v][i];
        }
    }
    return dp[u][0];
}
void solve() {
    int u,v,w;
    scanf("%d%d",&n,&m);
    init();
    for (int i = 1; i < n; i++) {
        scanf("%d%d%d",&u,&v,&w);
        add_edge(u,v,w);
        add_edge(v,u,w);
    }
    dfs(1,0);
    for (int i = 0; i < m; i++) {
        scanf("%d%d",&u,&v);
        printf("%d\n",dis[u] + dis[v] - 2 * dis[lca(u,v)]);
    }
}

int main() {   
    int T;
	cin >> T;
	for (int i = 1; i <= T; i++) {
		solve();
	}
    return 0;
}

HDU2874 Connections between cities

传送门:HDU2874 Connections between cities

题目大意

  n个城市,m条路,c个查询(2<=n<=10000, 0<=m<10000, 1<=c<=1000000)。两个城市之间可能没有道路,也无环。问:两个城市之间是否存在道路,如果不存在输出"Not connected",否则输出最短路径。

解题思路

  求森林中两点距离。还是可以用LCA,但是需要额外判断两点是否连通。
  判断两点是否连通一般是使用并查集维护集合。但是这里我们在使用LCA预处理dp数组时已经记录了某个结点的父亲结点是谁,当某个结点父亲结点是0时,说明它不在已知集合之中,则可以以它为根继续dfs建树。
  当查询LCA时,如果两个点的LCA(u,v) == 0,说明这两个点在不同集合,那么自然也就不连通了。
在这里插入图片描述

AC代码【倍增 + LCA】

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 1e4 + 5;
const int MAXM = 1e4 + 5;
const int INF = 0x3f3f3f3f;
int n,m,c,node,N;
int head[MAXN],dp[MAXN][30],deep[MAXN];
ll dis[MAXN];
struct Node {
    int to,w,pre;
}edge[MAXM << 1];
void init() {
    memset(head, -1, sizeof(head));
    memset(dp, 0, sizeof(dp));
    memset(dis, 0, sizeof(dis));
    memset(deep, 0, sizeof(deep));
    node = 0;
    N = (int)log2(n);
}
void add_edge(int u, int v, int w) {
    edge[node].to = v;
    edge[node].w = w;
    edge[node].pre = head[u];
    head[u] = node++;
}
void dfs(int now, int fa) {
    dp[now][0] = fa;
    for (int i = 1; (1 << i) <= deep[now]; i++) {
        dp[now][i] = dp[dp[now][i - 1]][i - 1];
    }
    for (int i = head[now]; ~i; i = edge[i].pre) {
        int v = edge[i].to;
        if (v != fa) {
            deep[v] = deep[now] + 1;
            dis[v] = dis[now] + edge[i].w;
            dfs(v,now);
        }
    }
}
int lca(int u, int v) {
    if (deep[u] < deep[v]) {
        swap(u,v);
    }
    int h = deep[u] - deep[v];
    for (int i = 0, j = (1 << i); j <= h; i++,j  = (1 << i)) {
        if (h & j) {
            u = dp[u][i];
        }
    }
    if (u == v) {
        return u;
    }
    for (int i = N; i >= 0; i--) {
        if (dp[u][i] != dp[v][i]) {
            u = dp[u][i];
            v = dp[v][i];
        }
    }
    return dp[u][0];
}
void solve() {
    int u,v,w;
    while (~scanf("%d%d%d",&n,&m,&c)) {
        init();
        for (int i = 0; i < m; i++) {
            scanf("%d%d%d",&u,&v,&w);
            add_edge(u,v,w);
            add_edge(v,u,w);
        }
        for (int i = 1; i <= n; i++) {
            if (!dp[i][0]) {
            	// 点 i 不在已知集合中,以 i 为根 dfs 建树
                dfs(i,0);
            }
        }
        for (int i = 0; i < c; i++) {
            scanf("%d%d",&u,&v);
            int  l = lca(u,v);
            if (l == 0) {
            	// lca(u, v) == 0 说明 u,v不在同一个集合,即不连通
                puts("Not connected");
            } else {
                printf("%lld\n",dis[u] + dis[v] - 2 * dis[l]);
            }
        }
    }
}
int main() {   
    solve();
    return 0;
}

CodeForces1328E Tree Queries

传送门:CodeForces1328E Tree Queries

题目大意

  给你n个顶点n-1条边的树,然后有m个查询,查询由一个数k和k个数组成,问是否存在从根到某个顶点u的路径,以使给定的k个顶点中的每个顶点都属于该路径,或者与该路径上的某个顶点之间的距离为1。

解题思路

  对于题目要求的路径,可以看做以根结点为起点,深度最深的点为终点,然后判断其他点是否在这条路径上,或者距离这条路径上某个点距离为1。
  怎么判断呢?找到某个点u和深度最深的点v的LCA(u,v),他们的LCA(u,v)必定在路径上,那么当deep[u] - deep[LCA(u,v)] <= 1时,就说明点u在路径上(u == LCA(u,v))或者离路径上的点距离为1。 当所有的点u都满足条件时为YES,有一个点不满足则为NO。

AC代码【倍增 + LCA + 思维】

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 2e5 + 5;
const int MAXM = 2e5 + 5;
const int INF = 0x3f3f3f3f;
int n,m,c,node,N;
int head[MAXN],dp[MAXN][30],deep[MAXN],a[MAXN];
struct Node {
    int to,pre;
}edge[MAXM << 1];
void init() {
    memset(head, -1, sizeof(head));
    memset(dp, 0, sizeof(dp));
    memset(deep, 0, sizeof(deep));
    node = 0;
    N = (int)log2(n);
}
void add_edge(int u, int v) {
    edge[node].to = v;
    edge[node].pre = head[u];
    head[u] = node++;
}
void dfs(int now, int fa) {
    dp[now][0] = fa;
    for (int i = 1; (1 << i) <= deep[now]; i++) {
        dp[now][i] = dp[dp[now][i - 1]][i - 1];
    }
    for (int i = head[now]; ~i; i = edge[i].pre) {
        int v = edge[i].to;
        if (v != fa) {
            deep[v] = deep[now] + 1;
            dfs(v,now);
        }
    }
}
int lca(int u, int v) {
    if (deep[u] < deep[v]) {
        swap(u,v);
    }
    int h = deep[u] - deep[v];
    for (int i = 0, j = (1 << i); j <= h; i++,j  = (1 << i)) {
        if (h & j) {
            u = dp[u][i];
        }
    }
    if (u == v) {
        return u;
    }
    for (int i = N; i >= 0; i--) {
        if (dp[u][i] != dp[v][i]) {
            u = dp[u][i];
            v = dp[v][i];
        }
    }
    return dp[u][0];
}
void solve() {
    int u,v,k;
    while (~scanf("%d%d",&n,&m)) {
        init();
        for (int i = 1; i < n; i++) {
            scanf("%d%d",&u,&v);
            add_edge(u,v);
            add_edge(v,u);
        }
        dfs(1,0);
        for (int i = 0; i < m; i++) {
            scanf("%d",&k);
            // vertices 存储深度最深的结点
            int vertices = 0;
            for (int j = 0; j < k; j++) {
                scanf("%d",&a[j]);
                if (deep[a[j]] > deep[vertices]) {
                    vertices = a[j];
                }
            }
            int flag = 1;
            for (int j = 0; j < k; j++) {
                int l = lca(a[j],vertices);
                // 点 a[j] 到路径上的点距离大于 1,不符合题意
                if (deep[a[j]] - deep[l] > 1) {
                    puts("NO");
                    flag = 0;
                    break;
                }
            }
            if (flag) {
                puts("YES");
            }
        }
    }
}

int main() {   
    solve();
    return 0;
}

CodeForces1304E 1-Trees and Queries

传送门;CodeForces1304E 1-Trees and Queries

题目大意

  给你有n个顶点n-1条边的树,然后有q个查询。 每个查询包含5个整数:x,y,a,b和k。 意为在顶点x和y之间添加双向边后,问是否存在从顶点a到b的路径,该路径恰好包含k个边。 路径可以多次包含相同的顶点和相同的边。 所有查询彼此独立,也就是说,在下一个查询时删除了以往查询添加的边。

解题思路

  对于给定的询问,可能存在三种路径:
  1、a->b
  2、a->x->y->b
  3、a->y->x->b
  对于三条路径任意一条满足距离小于等于k且和k同奇偶性为YES,三条都不满足为NO。
  距离小于等于k且同奇偶性是为了可以反复走某一路径使得凑够k条边。

AC代码【倍增 + LCA + 思维】

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 1e5 + 5;
const int MAXM = 1e5 + 5;
const int INF = 0x3f3f3f3f;
int n,m,c,node,N;
int head[MAXN],dp[MAXN][30],deep[MAXN],a[MAXN];
struct Node {
    int to,pre;
}edge[MAXM << 1];
void init() {
    memset(head, -1, sizeof(head));
    memset(dp, 0, sizeof(dp));
    memset(deep, 0, sizeof(deep));
    node = 0;
    N = (int)log2(n);
}
void add_edge(int u, int v) {
    edge[node].to = v;
    edge[node].pre = head[u];
    head[u] = node++;
}
void dfs(int now, int fa) {
    dp[now][0] = fa;
    for (int i = 1; (1 << i) <= deep[now]; i++) {
        dp[now][i] = dp[dp[now][i - 1]][i - 1];
    }
    for (int i = head[now]; ~i; i = edge[i].pre) {
        int v = edge[i].to;
        if (v != fa) {
            deep[v] = deep[now] + 1;
            dfs(v,now);
        }
    }
}
int lca(int u, int v) {
    if (deep[u] < deep[v]) {
        swap(u,v);
    }
    int h = deep[u] - deep[v];
    for (int i = 0, j = (1 << i); j <= h; i++,j  = (1 << i)) {
        if (h & j) {
            u = dp[u][i];
        }
    }
    if (u == v) {
        return u;
    }
    for (int i = N; i >= 0; i--) {
        if (dp[u][i] != dp[v][i]) {
            u = dp[u][i];
            v = dp[v][i];
        }
    }
    return dp[u][0];
}
int dis(int u, int v) {
    return deep[u] + deep[v] - 2 * deep[lca(u,v)];
}
bool check(int len, int k) {
    return (len <= k) && (len % 2 == k % 2);
}
void solve() {
    int u,v,x,y,a,b,k;
    while (~scanf("%d",&n)) {
        init();
        for (int i = 1; i < n; i++) {
            scanf("%d%d",&u,&v);
            add_edge(u,v);
            add_edge(v,u);
        }
        dfs(1,0);
        scanf("%d",&m);
        for (int i = 0; i < m; i++) {
            scanf("%d%d%d%d%d",&x,&y,&a,&b,&k);
            if (check(dis(a,b),k) || check(dis(x,a) + dis(y,b) + 1,k) || check(dis(y,a) + dis(x,b) + 1,k)) {
                puts("YES");
            } else {
                puts("NO");
            }
        }
    }
}

int main() {   
    solve();
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值