目录
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 depu−depv
- 其余情况,距离为 d e p u − d e p l c a + 1 dep_u - dep_{lca} + 1 depu−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
题意:
给你一棵树,每次询问给出五个整数
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+2∗i的路径。所以,当
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;
}