目录

  • A. Nearest Common Ancestors
  • B. Closest Common Ancestors
  • C. How far away ?
  • D. Connections between cities
  • E. CD操作
  • F. 1-Trees and Queries
  • G. Network

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

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

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

  • 点uuu和点vvv为同一节点,距离为000
  • 点uuu为点vvv的祖先节点,距离为111
  • 点vvv为点uuu的祖先节点,距离为depu−depvdep_u - dep_vdepu​−depv​
  • 其余情况,距离为depu−deplca+1dep_u - dep_{lca} + 1depu​−deplca​+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

题意:
给你一棵树,每次询问给出五个整数xxx, yyy, aaa, bbb, kkk,问你在xxx和yyy之间加上一条边后,从aaa到bbb是否有长度正好为kkk的路径,同一条边可被重复计算进路径长。

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

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

#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

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

题解:
很容易想到,新增一条边后,会在两点和他们的LCA之间形成一个环,想要通过删除这个环内一条原有边来达到目的,肯定要删除这条新增的边,对答案的贡献为111。当原有边能构成两个环时,删除这条原有边并不能对答案有贡献。
我们接下来考虑没有在环里的原有边。根据树的特性,我们只要删除这条原有边,就能让图不连通,所以可以删除任意一条新增边,对答案的贡献为mmm。
对于树来说,每个子节点只有一个父亲节点,所以我们如图所示,直接用点的编号来代表边的编号。

我们设每次在点uuu和点vvv之间新增一条边,那么点uuu到LCA(u,v)LCA_{(u, v)}LCA(u,v)​,和点vvv到LCA(u,v)LCA_{(u, v)}LCA(u,v)​之间的所有边的权值 + 111,用权值来表示这条边可以构成几个环。
我们一次要修改一个区间,又是在树上,很容易想到用dfs序 + 数据结构来实现。但是不连续区间较多,很容易卡到O(n * m * lognlog_nlogn​)。
我们又想到只要求最终的答案,根据树的性质,可以用树上差分来解决这个问题。

#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;
}

2021 HZNU Winter Camp -- LCA相关推荐

  1. 2021 HZNU Winter Training Day 17 (2018 German Collegiate Programming Contest (GCPC 18))

    2021 HZNU Winter Training Day 17 (2018 German Collegiate Programming Contest (GCPC 18)) 题目 A B C D E ...

  2. 2021 HZNU Winter Training Day 18

    目录 Solutions A. 星球大战starwar B. Median C. Obtain Two Zeroes D. Naive Operations E. Co-prime F. Lightn ...

  3. 2019年 CCPC-Wannafly Winter Camp 总结及感悟

    比赛网站:传送门 一.得到的知识训练 1. dp ①直接dp 数据范围很小(n<=100) 且符合动态规划的题目特征可直接暴力dp,直接用多维(一般三维以上)状态.多重(一般三重以上)for循环 ...

  4. CCPC-Wannafly Winter Camp Div2 Day2 自闭总结

    CCPC-Wannafly Winter Camp Day1 (Div2, onsite) 今天学了首歌 菜鸟泪 题目看错整场错 投身竞赛为了生活 菜鸡也是人 AC不了向谁说 为了奖牌的逼迫 颗颗泪水 ...

  5. CCPC-Wannafly Winter Camp Day1 自闭总结

    CCPC-Wannafly Winter Camp Day1 (Div2, onsite) 出现了巨大失误 E题原来的思路是预处理后求最大生成树的,后来发现点所连的边数不定(n<=100当然是可 ...

  6. 2019 CCPC-Wannafly Winter Camp Day8 (Div2, onsite) 补题记录

    一篇来自ACM入门者的补题记录 最近有点懒,想着还有最后一篇博客没完成,是我最大的补题动力. 不过终于在camp过去三个月的时候完成了所有的补题博客,有点欣慰,下一个目标应该是补一补一年前暑期训练的题 ...

  7. 2018ccpc-wannafly winter camp div2.Day1

    今天是自闭的第一天,上午的开营仪式wls从生物演化的角度切入,让我们思考了几个问题"为什么学算法","是什么阻碍你学算法"......听完感觉自己又懒又菜,其实 ...

  8. CCPC-Wannafly Winter Camp 总结

    回家两天了.看了两天的孩子..真是累!!比学习都累,,感觉看了两天孩子,整个人都要变傻了,明天终于不用我看了,,今天就先总结一下游秦皇岛一个周的训练心得, 1.19晚我们抵达秦皇岛,看到很多烧烤店,胃 ...

  9. 2020 CCPC-Wannafly Winter Camp Day1 总结

    部分题目列表: 题号 题目 算法 B题 密码学 签到题,字符串处理 H题 最大公约数 数学.gcd.大数 F题 乘法 较为巧妙的使用二分 A题 期望逆序对 - C题 染色图 - I题 K小数查询 在线 ...

最新文章

  1. Docker 用法总结之:管理工具 shipyard 的具体使用指南
  2. 架构师技术文档:Redis+Nginx+Spring全家桶+Dubbo精选
  3. 利用WiFi模块实现MicroPython远程开发
  4. php 根据位置显示地图,php通过地址获得百度地图经纬度(地理编码)
  5. 此异常最初是在此调用堆栈中引发的:_【8】进大厂必须掌握的面试题Java面试异常和线程...
  6. AI应用开发基础傻瓜书系列3-激活函数和损失函数
  7. PHP 生成csv的遇到的分隔符问题
  8. 动态ip解析 linux,ddwrt路由/linux动态解析ip(ddns)到dnspod配置
  9. adb命令启动activity、service,发送broadcast
  10. 在vue.js中如何导出Excel表格
  11. 【中南林业大学第十一届校赛E:】砝码和天平(转化为进制问题+模拟进位)
  12. 【白板动画制作软件】万彩手影大师教程 | 分享在线视频
  13. JAVA计算机毕业设计多媒体素材管理系统Mybatis+系统+数据库+调试部署
  14. SpringBoot+zxing批量生成二维码_南国
  15. 华为手机鸿蒙切换主页,京东APP可一键切换“华为鸿蒙版界面”:简洁多了
  16. arm版红帽企业linux,红帽:ARM架构下的Linux不是玩笑
  17. 计算机模拟需要什么配置电脑,网易MuMu模拟器对电脑配置的最低要求介绍
  18. 【微信小程序】微信小程序项目+仿哔哩哔哩b站
  19. mysql字段掩码_什么叫掩码
  20. 加密资产走过十年,正是区块链技术觉醒的开始

热门文章

  1. 单细胞测序数据整合(Seurat V4.0) vignettes
  2. 逻辑回归模型混淆矩阵评价与ROC曲线最佳阈值的好处(附Accuracy,TPR,FPR计算函数)
  3. 英特尔快速存储技术 linux,英特尔快速存储技术企业版(CTRL+I)RAID配置演示1
  4. 安卓使用javascriptApi快速开发高德地图
  5. 2013 7 5新鲜出炉 Ubuntu12 04下载Android4 0 1源码全过程---------------
  6. 智慧树python程序设计答案_智慧树知道Python程序设计完整答案
  7. unity学习路线参考——转载自csdn
  8. 站上智能汽车产业高地,合肥到底是“赌”来的,还是另有蹊径?
  9. 自清洁半导体异质结衬底
  10. 公用计算机配置文件,配置公用和专用计算机文件访问