2021 HZNU Winter Camp -- LCA

A. Nearest Common Ancestors

原题链接:POJ 1330

题解:
LCA裸题。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <ctime>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <iomanip>

using namespace std;

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N =1e4 + 7;

int n, root, u, v;
vector<int> G[N];
int par[N][20], dep[N], deg[N];

void dfs(int u, int fa) {
    dep[u] = dep[fa] + 1;
    par[u][0] = fa;
    for (int i = 1; i < 20; ++i) par[u][i] = par[ par[u][i - 1] ][i - 1];
    for (int i = 0; i < G[u].size(); ++i) {
        int v = G[u][i];
        if (v == fa) continue;
        dfs(v, u);
    }
}

int lca(int u, int v) {
    if (dep[u] < dep[v]) swap(u, v);
    for (int i = 19; i >= 0; --i) {
        if (dep[ par[u][i] ] >= dep[v]) {
            u = par[u][i];
        }
    }
    if (u == v) return u;
    for (int i = 19; i >= 0; --i) {
        if (par[u][i] != par[v][i]) {
            u = par[u][i];
            v = par[v][i];
        }
    }
    return par[u][0];
}

void solve() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) G[i].clear(), deg[i] = 0;
    for (int i = 1; i < n; ++i) {
        scanf("%d %d", &u, &v);
        G[u].push_back(v);
        deg[v]++;
    }
    for (int i = 1; i <= n; ++i) {
        if (deg[i] == 0) {
            root = i;
            break;
        }
    }
    dfs(root, 0);
    scanf("%d %d", &u, &v);
    printf("%d\n", lca(u, v));
}

int main() {
    int t = 1;
    scanf("%d", &t);
    while (t--) solve();
}

B. Closest Common Ancestors

原题链接:POJ 1470

题解:
注意格式化输入。我们最终输出所有询问后各个点成为LCA的次数,所以不用记录问题的编号。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <ctime>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <iomanip>

using namespace std;

#define outtime() cerr<<"User Time = "<<(double)clock()/CLOCKS_PER_SEC<<endl
#define endl "\n"

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-7;
const int N = 1e3 + 7;

int n, m, num, u, v, vis[N], ans[N], fa[N], indeg[N];
vector<int> G[N], q[N];

int find(int x) { return x == fa[x]? x : fa[x] = find(fa[x]); }

void merge(int x, int y) {
    int fx = find(x), fy = find(y);
    fa[fx] = fy;
}
void dfs(int u, int fa) {
    vis[u] = 1;
    for (int i = 0; i < q[u].size(); ++i) {
        int v = q[u][i];
        if (vis[v]) {
            ans[find(v)]++;
        }
    }
    for (int i = 0; i < G[u].size(); ++i) {
        int v = G[u][i];
        if (v == fa) continue;
        dfs(v, u);
        merge(v, u);
    }
}

void solve() {
	for (int i = 1; i <= n; ++i) G[i].clear(), q[i].clear(), vis[i] = 0, ans[i] = 0, fa[i] = i, indeg[i] = 0;
    for (int i = 1; i <= n; ++i) {
    	scanf("%d:(%d)", &u, &num);
        while (num--) {
            scanf("%d", &v);
            indeg[v]++;
            G[u].push_back(v);
        }
	}
    scanf("%d", &m);
    while (m--) {
        scanf(" (%d %d)", &u, &v); // 读入时前面加个空格
        q[u].push_back(v);
        q[v].push_back(u);
    }
    for (int i = 1; i <= n; ++i) {
        if (indeg[i] == 0) {
            dfs(i, 0);
            break;
        }
    }
    for (int i = 1; i <= n; ++i) {
        if (ans[i]) printf("%d:%d\n", i, ans[i]);
    }
}

int main() {
	while (~scanf("%d", &n)) solve();
	return 0;
}

C. How far away ?

原题链接:HDU 2586

题意:
带边权值的求树上两点距离。

题解:
使用tarjan做法的话,不用把两个点的高度调整到一样高,所以深度数组直接用来记录节点到根节点的距离。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <ctime>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <iomanip>

using namespace std;

#define outtime() cerr<<"User Time = "<<(double)clock()/CLOCKS_PER_SEC<<endl
#define endl "\n"

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-7;
const int N = 4e4 + 7;

int n, m, num, u, v, w, dep[N], ans[N], fa[N], indeg[N];

vector<int> q[N], qid[N];
vector< pair<int, int> > G[N];

int find(int x) { return x == fa[x]? x : fa[x] = find(fa[x]); }

void merge(int x, int y) {
    int fx = find(x), fy = find(y);
    fa[fx] = fy;
}
void dfs(int u, int fa, int dis) {
    dep[u] = dep[fa] + dis;
    for (int i = 0; i < q[u].size(); ++i) {
        int v = q[u][i];
        int id = qid[u][i];
        if (dep[v] != 0) {
            ans[id] = dep[u] + dep[v] - 2 * dep[find(v)];
        }
    }
    for (int i = 0; i < G[u].size(); ++i) {
        int v = G[u][i].first;
        int w = G[u][i].second;
        if (v == fa) continue;
        dfs(v, u, w);
        merge(v, u);
    }
}

void solve() {
    scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; ++i) {
        G[i].clear(), q[i].clear(), qid[i].clear(), dep[i] = 0, fa[i] = i, indeg[i] = 0;
    }
    for (int i = 1; i < n; ++i) {
        scanf("%d %d %d", &u, &v, &w);
        G[u].push_back({v, w});
        G[v].push_back({u, w});
        indeg[v]++;
    }
    for (int i = 1; i <= m; ++i) {
        ans[i] = 0;
        scanf("%d %d", &u, &v);
        q[u].push_back(v);
        qid[u].push_back(i);
        q[v].push_back(u);
        qid[v].push_back(i);
    }
    for (int i = 1; i <= n; ++i) {
        if (indeg[i] == 0) {
            dfs(i, 0, 1);
            break;
        }
    }
    for (int i = 1; i <= m; ++i) {
        printf("%d\n", ans[i]);
    }
}

int main() {
    int t = 1;
    scanf("%d", &t);
	while (t--) solve();
	return 0;
}

D. Connections between cities

原题链接:HDU 2874

题意:
求带边权值多棵树之间点的距离。

题解:
直接用tarjan + vector存邻接表写会MLE,因为vector开空间时很容易开很大,如果要用tarjan写请使用链式前向星存储。这里用在线的方法写。
我们要先考虑两个点是否在一棵树上,这里用并查集来实现。本题只需要输出两点间的距离,而没有问LCA具体是哪个点。对于求距离来说,我们只要选择一个点作为树的根节点,求出来的结果是一样的。所以我们直接选取并查集的根节点作为树的根节点,处理出每个节点的深度和节点到根节点的距离,就可以解决这个问题了。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <ctime>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <iomanip>

using namespace std;

#define outtime() cerr<<"User Time = "<<(double)clock()/CLOCKS_PER_SEC<<endl
#define endl "\n"

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-7;
const int N = 1e4 + 7;
const int Q = 1e6 + 7;

int n, m, c, fa[N], par[N][20], dep[N];
int u, v, w, dis[N], vis[N];
vector< pair<int, int> > G[N];

int find(int x) {
	return x == fa[x]? x : fa[x] = find(fa[x]);
}

void merge(int x, int y) {
	fa[find(x)] = find(y);
}

void init() {
    for (int i = 1; i <= n; ++i) {
    	vis[i] = 0;
    	fa[i] = i;
    	G[i].clear();
	}
}

void dfs(int u, int fa, int w) {
	dep[u] = dep[fa] + 1;
	dis[u] = dis[fa] + w;
	par[u][0] = fa;
	for (int i = 1; i < 20; ++i) {
		par[u][i] = par[ par[u][i - 1] ][i - 1];
	}
	for (int i = 0; i < G[u].size(); ++i) {
	    int v = G[u][i].first, w = G[u][i].second;
	    if (v == fa) continue;
	    dfs(v, u, w);
	}
}

int lca(int u, int v) {
	if (dep[u] < dep[v]) swap(u, v);
	for (int i = 19; i >= 0; --i) {
		if (dep[ par[u][i] ] >= dep[v]) u = par[u][i];
	}
	if (u == v) return u;
	for (int i = 19; i >= 0; --i) {
		if (par[u][i] != par[v][i]) {
			u = par[u][i], v = par[v][i];
		}
	}
	return par[u][0];
}

void solve() {
    init();
    for (int i = 1; i <= m; ++i) {
    	scanf("%d %d %d", &u, &v, &w);
    	G[u].push_back({v, w});
    	G[v].push_back({u, w});
    	merge(u, v);
	}
	for (int i = 1; i <= n; ++i) {
		if (!vis[find(i)]) dfs(i, 0, 1);
		vis[find(i)] = 1;
	}
	while (c--) {
		scanf("%d%d", &u, &v);
		if (find(u) != find(v)) puts("Not connected");
		else printf("%d\n", dis[u] + dis[v] - 2 * dis[lca(u, v)]);
	}
}

int main() {
	while (~scanf("%d %d %d", &n, &m, &c)) solve();
	return 0;
}

E. CD操作

原题链接:HDU 4547

题意:
给你一个有向带根节点的树,祖先节点到子节点的距离为 1 1 1,子节点到祖先节点的距离为正常距离,问两点之间的距离。

题解:
因为边是有向的,且方向对于最终的值有影响,用离线tarjan来实现会稍微麻烦一些。这里采用在线倍增来写。点的编号被抽象成了文件名,我们用map来实现文件名到点编号的转换。
我们设 d e p dep dep数组来记录每个点的深度。对于每组点 u u u到点 v v v的距离询问,我们注意到可以分成以下几种情况:

  • u u u和点 v v v为同一节点,距离为 0 0 0
  • u u u为点 v v v的祖先节点,距离为 1 1 1
  • v v v为点 u u u的祖先节点,距离为 d e p u − d e p v dep_u - dep_v depudepv
  • 其余情况,距离为 d e p u − d e p l c a + 1 dep_u - dep_{lca} + 1 depudeplca+1
#include <bits/stdc++.h>

using namespace std;

#define endl "\n"

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 7;

int n, m, dep[N], par[N][20], indeg[N];
vector<int> G[N];
string a, b;
map<string, int> mp;

void dfs(int u, int fa) {
    dep[u] = dep[fa] + 1;
    par[u][0] = fa;
    for (int i = 1; i < 20; ++i) {
        par[u][i] = par[ par[u][i - 1] ][i - 1];
    }
    for (auto v : G[u]) {
        if (v == fa) continue;
        dfs(v, u);
    }
}

int lca(int u, int v) {
    if (dep[u] < dep[v]) swap(u, v);
    for (int i = 19; i >= 0; --i) {
        if (dep[ par[u][i] ] >= dep[v]) {
            u = par[u][i];
        }
    }
    if (u == v) return u;
    for (int i = 19; i >= 0; --i) {
        if (par[u][i] != par[v][i]) {
            u = par[u][i];
            v = par[v][i];
        }
    }
    return par[u][0];
}

int getDis(int u, int v) {
    if (u == v) return 0;
    int fa = lca(u, v);
    if (fa == u) {
        return 1;
    }
    if (fa == v) {
        return dep[u] - dep[v];
    }
    return dep[u] - dep[fa] + 1;
}

void solve() {
    cin >> n >> m;
    int tot = 0;
    mp.clear();
    for (int i = 1; i <= n; ++i) {
        G[i].clear();
        dep[i] = 0;
    }
    for (int i = 1; i < n; ++i) {
        cin >> a >> b;
        if (!mp.count(a)) mp[a] = ++tot;
        if (!mp.count(b)) mp[b] = ++tot;
        G[ mp[b] ].push_back(mp[a]);
        indeg[mp[a]]++;
    }
    for (int i = 1; i <= tot; ++i) {
        if (indeg[i] == 0) {
            dfs(i, 0);
            break;
        }
    }
    for (int i = 1; i <= m; ++i) {
        cin >> a >> b;
        cout << getDis(mp[a], mp[b]) << endl;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);cout.tie(nullptr);
    cout << fixed << setprecision(20);
    int t = 1;
    cin >> t;
    while (t--) solve();
}

F. 1-Trees and Queries

原题链接:Codeforces Round #620 (Div.2) E

题意:
给你一棵树,每次询问给出五个整数 x x x, y y y, a a a, b b b, k k k,问你在 x x x y y y之间加上一条边后,从 a a a b b b是否有长度正好为 k k k的路径,同一条边可被重复计算进路径长。

题解:
我们设 a a a b b b的路径长度为 z z z。显然,我们可以得到所有长度为 z + 2 ∗ i z + 2 * i z+2i的路径。所以,当 z z z% 2 2 2的值与 k k k% 2 2 2的值相等,并且 z z z ≤ \leq k k k时,我们不需要借助添加的边即可得到答案。

当我们借助新添加的边时,我们发现从 a a a b b b有两种路径可以选,即 a − > x − > y − > b a->x->y->b a>x>y>b a − > y − > x − > b a->y->x->b a>y>x>b。不难发现长的路径与短的路径相差的长度为偶数,这部分长度对于答案是没有贡献的,因为我们在判断时会取余 2 2 2。所以我们就借助短的路径来计算。
同理,我们只用借助一次新添加的边就好了。通过这一次借助的操作,我们就可以改变路径的奇偶性,或者减少路径长度来实现路径长度正好为 k k k,也没有必要借助更多次了。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 7;

int n, m, dep[N];
int par[N][20];
vector<int> G[N];

void dfs(int u, int fa) {
    dep[u] = dep[fa] + 1;
    par[u][0] = fa;
    for (int i = 1; i < 20; ++i) {
        par[u][i] = par[ par[u][i - 1] ][i - 1];
    }
    for (auto v : G[u]) {
        if (v == fa) continue;
        dfs(v, u);
    }
}

int getDis(int u, int v) {
    if (dep[u] < dep[v]) swap(u, v);
    int t = dep[u] + dep[v];
    for (int i = 19; i >= 0; --i) {
        if (dep[ par[u][i] ] >= dep[v]) {
            u = par[u][i];
        }
    }
    
    if (u == v) return t - dep[u] * 2;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
    
    for (int i = 19; i >= 0; --i) {
        if (par[u][i] != par[v][i]) {
            u = par[u][i];
            v = par[v][i];
        }
    }
    return t - dep[u] * 2 + 2;
}

void solve() {
    scanf("%d", &n);
    for (int i = 1, u, v; i < n; ++i) {
        scanf("%d %d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1, 0);

    cin >> m;
    for (int i = 1, x, y, a, b, k; i <= m; ++i) {
        scanf("%d %d %d %d %d", &x, &y, &a, &b, &k);
        int ans = inf;

        int without = getDis(a, b); // 没有借助新边
        if (without % 2 == k % 2) ans = without;

        int with = min(getDis(x, a) + getDis(y, b), getDis(y, a) + getDis(x, b)) + 1; // 借助新边
        if (with % 2 == k % 2) ans = min(ans, with);

        if (ans <= k) puts("YES");
        else puts("NO");
    }
}

int main() {
    int t = 1;
    while (t--) solve();
}

G. Network

原题链接:POJ 3417

题意:
给你一棵树,再给你 m m m条附加的边,问你有多少种方案,各删除一条原有的边和附加的边后,使得图不连通。

题解:
很容易想到,新增一条边后,会在两点和他们的LCA之间形成一个环,想要通过删除这个环内一条原有边来达到目的,肯定要删除这条新增的边,对答案的贡献为 1 1 1。当原有边能构成两个环时,删除这条原有边并不能对答案有贡献。
我们接下来考虑没有在环里的原有边。根据树的特性,我们只要删除这条原有边,就能让图不连通,所以可以删除任意一条新增边,对答案的贡献为 m m m
对于树来说,每个子节点只有一个父亲节点,所以我们如图所示,直接用点的编号来代表边的编号。
在这里插入图片描述
我们设每次在点 u u u和点 v v v之间新增一条边,那么点 u u u L C A ( u , v ) LCA_{(u, v)} LCA(u,v),和点 v v v L C A ( u , v ) LCA_{(u, v)} LCA(u,v)之间的所有边的权值 + 1 1 1,用权值来表示这条边可以构成几个环。
我们一次要修改一个区间,又是在树上,很容易想到用dfs序 + 数据结构来实现。但是不连续区间较多,很容易卡到O(n * m * l o g n log_n logn)。
我们又想到只要求最终的答案,根据树的性质,可以用树上差分来解决这个问题。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <ctime>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <iomanip>

using namespace std;

#define outtime() cerr<<"User Time = "<<(double)clock()/CLOCKS_PER_SEC<<endl
#define endl "\n"

typedef long long ll;

const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-7;
const int N = 1e5 + 7;

int n, m, par[N][20], dep[N], sum[N];
int u, v, tot, head[N];

struct Edge {
	int nxt, to;
	
	Edge () {}
	Edge (int nxt, int to) : nxt(nxt), to(to) {}
}edge[N << 1];

void dfs(int u, int fa) {
	dep[u] = dep[fa] + 1;
	par[u][0] = fa;
	for (int i = 1; i < 20; ++i) {
		par[u][i] = par[ par[u][i - 1] ][i - 1];
	}
	for (int i = head[u]; ~i; i = edge[i].nxt) {
		int v = edge[i].to;
	    if (v == fa) continue;
	    dfs(v, u);
	}
}

void getSum(int u, int fa) {
	for (int i = head[u]; ~i; i = edge[i].nxt) {
		int v = edge[i].to;
		if (v == fa) continue;
		getSum(v, u);
		sum[u] += sum[v];
	}
}

int lca(int u, int v) {
	if (dep[u] < dep[v]) swap(u, v);
	for (int i = 19; i >= 0; --i) {
		if (dep[ par[u][i] ] >= dep[v]) u = par[u][i];
	}
	if (u == v) return u;
	for (int i = 19; i >= 0; --i) {
		if (par[u][i] != par[v][i]) {
			u = par[u][i], v = par[v][i];
		}
	}
	return par[u][0];
}

void addedge(int u, int v) {
	edge[tot] = Edge(head[u], v);
	head[u] = tot++;
	edge[tot] = Edge(head[v], u);
	head[v] = tot++;
}

void solve() {
	memset(head, -1, sizeof(head));
    for (int i = 1; i < n; ++i) {
    	scanf("%d %d", &u, &v);
    	addedge(u, v);
	}
	dfs(1, 0);
	for (int i = 1; i <= m; ++i) {
		scanf("%d %d", &u, &v);
		sum[u]++;
		sum[v]++;
		sum[lca(u, v)] -= 2;
	}
	getSum(1, 0);
	int ans = 0;
	for (int i = 2; i <= n; ++i) {
		if (sum[i] == 0) ans += m;
		if (sum[i] == 1) ans += 1;
	}
	printf("%d\n", ans);
}

int main() {
	scanf("%d %d", &n, &m);
	solve();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值