JOISC2020 Day 4 A 首都 題解

bluewindde發表於2024-08-16

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

相關文章