JOISC2020 Day 4 A 首都
JOI AtCoder Luogu
考慮一條鏈的情形。
如圖,將每個城市視為一條線段,容易發現交錯(有交但不包含)的若干線段必須全部合併才能符合條件。但如果這麼寫會出錯,原因是線段有包含關係,外層線段需要統計內層線段的答案,但內層線段不需要統計外層線段的答案。如果設內層線段為 \(x\),外層線段為 \(y\),則可以這樣描述:
- 如果選 \(x\) 作為首都,則不能選 \(y\)(選 \(y\) 一定更劣);
- 如果選 \(y\) 作為首都,則必須選 \(x\)(滿足作為首都的條件)。
這很像 2-SAT 問題,啟發我們根據依賴關係建圖:如果城市 \(a\) 的兩個城鎮之間的路徑經過城市 \(b\),則連邊 \(a \rightarrow b\)。答案即為沒有出邊(滿足條件不依賴於合併更多城市)的最小的強連通分量的大小減 \(1\)。
暴力連邊一定會超時,考慮最佳化建圖:若當前對於城市 \(x\) 建圖,類似於建虛樹,取出 \(x\) 的所有城鎮以及它們的所有 LCA 作為關鍵點。關鍵點之間使用樹鏈剖分得到一段連續的鏈,將 \(x\) 向鏈上的點所屬的城市分別連邊。
使用 線段樹最佳化建圖,在樹剖得到的 DFS 序上建立線段樹,每個葉子節點代表向其代表的原樹上的點所屬的城市連邊,線段樹內部父親向兒子連邊。如此操作後,每條鏈 \([l, r]\) 均可分解為至多 \(O(\log n)\) 個極大區間。可以發現,新圖一定與原圖等價,但邊的數量減小為可接受級別。
求強連通分量使用 Tarjan。
時間複雜度 \(O(n \log^2 n)\)。
#include <algorithm>
#include <iostream>
#include <set>
#include <vector>
using namespace std;
int n, k, rt;
int c[1000005];
vector<int> vec[1000005];
vector<int> G[1000005];
vector<int> include[1000005];
int f[1000005];
int dfn[1000005], dfn_clock;
int nfd[1000005];
int dep[1000005];
int son[1000005];
int top[1000005];
int siz[1000005];
static inline void dfs(int u, int fa) { // chain segmentation
f[u] = fa;
dep[u] = dep[fa] + 1;
siz[u] = 1;
for (auto v : vec[u]) {
if (v == fa)
continue;
dfs(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]])
son[u] = v;
}
}
static inline void dfs2(int u) {
dfn[u] = ++dfn_clock;
nfd[dfn_clock] = u;
if (!son[u])
return;
top[son[u]] = top[u];
dfs2(son[u]);
for (auto v : vec[u]) {
if (v == f[u] || v == son[u])
continue;
top[v] = v;
dfs2(v);
}
}
static inline int LCA(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]])
swap(u, v);
u = f[top[u]];
}
if (dep[u] < dep[v])
return u;
return v;
}
struct node { // SGT optimize building graph
int ls, rs;
} d[4000005];
int cnt;
static inline int build(int s, int t) {
int p = ++cnt;
if (s == t) {
G[p].push_back(c[nfd[s]]);
return p;
}
int mid = (s + t) >> 1;
d[p].ls = build(s, mid);
d[p].rs = build(mid + 1, t);
G[p].push_back(d[p].ls);
G[p].push_back(d[p].rs);
return p;
}
static inline void addedge(int l, int r, int s, int t, int from, int p) {
if (l <= s && r >= t) {
G[from].push_back(p);
return;
}
int mid = (s + t) >> 1;
if (l <= mid)
addedge(l, r, s, mid, from, d[p].ls);
if (r > mid)
addedge(l, r, mid + 1, t, from, d[p].rs);
}
static inline void add(int u, int v) {
int col = c[u];
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]])
swap(u, v);
addedge(dfn[top[u]], dfn[u], 1, n, col, rt);
u = f[top[u]];
}
if (dep[u] > dep[v])
swap(u, v);
addedge(dfn[u], dfn[v], 1, n, col, rt);
}
int t_dfn[1000005], t_low[1000005], t_dfn_clock;
int sta[1000005], tail;
bool insta[1000005];
vector<int> scc[1000005];
int belong[1000005];
int scc_cnt;
static inline void tarjan(int u) {
t_dfn[u] = t_low[u] = ++t_dfn_clock;
sta[++tail] = u;
insta[u] = true;
for (auto v : G[u]) {
if (!t_dfn[v]) {
tarjan(v);
t_low[u] = min(t_low[u], t_low[v]);
} else if (insta[v])
t_low[u] = min(t_low[u], t_dfn[v]);
}
if (t_dfn[u] == t_low[u]) {
++scc_cnt;
while (sta[tail] != u) {
scc[scc_cnt].push_back(sta[tail]);
belong[sta[tail]] = scc_cnt;
insta[sta[tail]] = false;
--tail;
}
scc[scc_cnt].push_back(u);
belong[u] = scc_cnt;
insta[u] = false;
--tail;
}
}
int sum[1000005];
int deg[1000005];
signed main() {
#ifndef ONLINE_JUDGE
freopen("P7215.in", "r", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
cnt = k;
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
vec[u].push_back(v);
vec[v].push_back(u);
}
for (int i = 1; i <= n; ++i) {
cin >> c[i];
include[c[i]].push_back(i);
}
dfs(1, 0);
top[1] = 1;
dfs2(1);
rt = build(1, n);
for (int i = 1; i <= k; ++i) {
if (include[i].size() > 1) { // like virtual tree
int cur = include[i][0];
for (size_t j = 1; j < include[i].size(); ++j) {
int lca = LCA(include[i][0], include[i][j]);
if (include[i][j] != lca)
add(include[i][j], lca);
if (dep[lca] < dep[cur])
cur = lca;
}
if (include[i][0] != cur)
add(include[i][0], cur);
}
}
for (int i = 1; i <= cnt; ++i)
if (!t_dfn[i])
tarjan(i);
for (int i = 1; i <= k; ++i)
++sum[belong[i]];
for (int u = 1; u <= cnt; ++u)
for (auto v : G[u])
if (belong[u] != belong[v])
++deg[belong[u]];
int ans = 1e9;
for (int i = 1; i <= k; ++i)
if (!deg[belong[i]])
ans = min(ans, sum[belong[i]]);
cout << ans - 1 << endl;
return 0;
}