题解 P6765 【[APIO2020]交换城市】
Subtask 1
「每个城市至多是两条道路的一个端点」说明只存在两种情况:一是整张图构成一条链,这时答案显然始终为
Subtask 2
菊花图的处理方法也很简单,分为两种情况:
一是
二是
Subtask 3
我们可以用一个类似 Dijkstra 的算法来尝试解决这个部分分。
用
比如现在我们已经有
时间复杂度
Subtask 4
回想 Subtask 1, 2,我们可以得到,两个城市可以互相到达当且仅当下面两个条件之一成立:
- 构成环;
- 某结点度数
\ge 3 。
总结一下,就是所在连通块不为链即可。
由于本题求的是瓶颈边,考虑利用 Kruskal 算法求最小生成树的过程。
我们按照
当我们插入某一条边后
问题在于
如果所有边都插入了终止条件还是不满足,答案为
时间复杂度
subtask 5
图的形态是一棵树,那么我们就不用考虑构成环的情况了。
用
如果有一条边连接了
最终,答案就是
时间复杂度
subtask 6
Subtask 4 的做法是比较靠谱的,可惜它需要每次询问都做一次 Kruskal。所以,我们可以尝试使用 Kruskal 重构树的思路。
普通的 Kruskal 重构树可以回答
所以,在
比如现在加入一条边
- 如果之前
A, B 已经连通且是链,那么加上这条边就不再是链了,打上标记,同时在 Kruskal 重构树上连边; - 如果原来
A, B 两个连通块有至少一个不是链,那么新连通块也一定不是链,打上标记,同时在 Kruskal 重构树上连边; - 如果原来
A, B 两个连通块都是链,那么新连通块是不是链取决于连边是不是在端点之间进行的。可以维护s_u 和t_u 表示u 为代表元的链的端点,那么这里就很好判断:- 如果不是在端点进行的,那么加上这条边就不再是链了,打上标记,同时在 Kruskal 重构树上连边;
- 如果是在端点进行的,那么加上这条边依然是链,更新
s 和t ,同时合并\text{pnt} 。
时间复杂度
代码中结点编号从
#include "swap.h"
#include <cassert>
#include <cstdio>
#include <algorithm>
#include <string>
#include <vector>
using namespace std;
const int MAXN = 500005;
int n, m, cnt, f[20][MAXN], dep[MAXN], s[MAXN], t[MAXN], rt[MAXN];
vector<int> pnt[MAXN], tr[MAXN];
bool nch[MAXN];
struct disjoint_sets_union
{
int fa[MAXN];
void init() { for(int i = 1; i <= n; i++) fa[i] = i; }
int find(int u) { return fa[u] == u ? u : fa[u] = find(fa[u]); }
} dsu;
struct edge
{
int u, v, w;
bool operator < (const edge &oth) const { return w < oth.w; }
} e[MAXN];
void DFS(int u, int fa)
{
dep[u] = dep[fa] + 1;
rt[u] = fa ? rt[fa] : u;
f[0][u] = fa;
for(int k = 1; k <= 19; k++) f[k][u] = f[k - 1][f[k - 1][u]];
for(int v : tr[u]) DFS(v, u);
}
int LCA(int u, int v)
{
if(rt[u] != rt[v]) return -1;
if(dep[u] < dep[v]) swap(u, v);
for(int k = 19; ~k; k--)
{
if(dep[f[k][u]] < dep[v]) continue;
u = f[k][u];
}
if(u == v) return u;
for(int k = 19; ~k; k--)
{
if(f[k][u] == f[k][v]) continue;
u = f[k][u]; v = f[k][v];
}
return f[0][u];
}
void init(int N, int M, vector<int> U, vector<int> V, vector<int> W)
{
n = N; m = M;
for(int i = 0; i < m; i++) e[++cnt] = (edge){U[i] + 1, V[i] + 1, W[i]};
sort(e + 1, e + m + 1);
dsu.init();
for(int i = 1; i <= n; i++) { s[i] = t[i] = rt[i] = i; pnt[i].push_back(i); }
for(int i = 1; i <= m; i++)
{
int u = e[i].u, v = e[i].v;
int fau = dsu.find(u), fav = dsu.find(v);
if(pnt[fau].size() < pnt[fav].size()) { swap(u, v); swap(fau, fav); }
int cur = i + n;
if(fau == fav)
{
if(!nch[fau])
{
nch[fau] = true;
for(int k : pnt[fau]) tr[cur].push_back(k);
pnt[fau].clear();
rt[fau] = cur;
}
}
else
{
if(nch[fau] || nch[fav])
{
if(nch[fau]) tr[cur].push_back(rt[fau]);
else for(int k : pnt[fau]) tr[cur].push_back(k);
if(nch[fav]) tr[cur].push_back(rt[fav]);
else for(int k : pnt[fav]) tr[cur].push_back(k);
pnt[fau].clear(); pnt[fav].clear();
nch[fau] = true;
rt[fau] = cur;
dsu.fa[fav] = fau;
}
else
{
if((u == s[fau] || u == t[fau]) && (v == s[fav] || v == t[fav]))
{
s[fau] = u ^ s[fau] ^ t[fau]; t[fau] = v ^ s[fav] ^ t[fav];
for(int k : pnt[fav]) pnt[fau].push_back(k);
pnt[fav].clear();
dsu.fa[fav] = fau;
}
else
{
nch[fau] = true;
for(int k : pnt[fau]) tr[cur].push_back(k);
for(int k : pnt[fav]) tr[cur].push_back(k);
pnt[fau].clear(); pnt[fav].clear();
rt[fau] = cur;
dsu.fa[fav] = fau;
}
}
}
}
for(int i = n + m; i; i--)
if(!dep[i]) DFS(i, 0);
}
int getMinimumFuelCapacity(int X, int Y)
{
int a = LCA(X + 1, Y + 1);
if(a == -1) return -1;
return e[a - n].w;
}