CF1361E James and the Chase 題解

下蛋爷發表於2024-04-08

Description

給定一個有 \(n\) 個點 \(m\) 條邊的有向強連通圖。稱一個點是好的當且僅當它到其他點都有且只有一條簡單路徑。如果好的點至少有 \(20\%\) 則輸出所有好的點,否則輸出 -1

單個測試點內有多組資料。
\(1\leq T\leq 2\times 10^3,1\leq n\leq 10^5,1\leq m\leq 2\times 10^5,\sum n\leq 10^5,\sum m\leq 2\times 10^5\)

Solution

考慮如何判斷一個點是否是好的。

先以一個點 \(x\) 為根建出 dfs 樹,如果建不出來顯然不合法,並且如果一條非樹邊不是返祖邊,就說明存在橫叉邊,顯然不合法。

容易發現滿足上面的兩個條件就一定合法,但是這樣做單次是 \(O(n)\) 的,過不了。

先不妨假設 \(x\) 是好點,那麼對於一個點 \(y\),如果他是好點,就說明其子樹裡有且僅有 \(1\) 條返祖邊其祖先為 \(y\) 的祖先,設其為 \(z\to w\)

然後有一個性質:如果 \(y\) 是好點當且僅當 \(w\) 是好點。

證明:

如果 \(y\) 是好點,由於 \(w\to y\) 只有一條路徑,所以 \(w\to y\) 的子樹的路徑都有且僅有一條,並且 \(y\) 要出 \(w\) 的子樹就必須經過 \(w\),由於 \(y\to w\) 子樹外的點的路徑都是唯一的,所以 \(w\to w\) 子樹外的點的路徑也是唯一的,所以 \(w\) 是好點。

如果 \(w\) 是好點,由於 \(w\) 到所有點路徑唯一且 \(y\)\(w\) 子樹必須經過 \(w\),所以 \(y\) 也是好點。

所以先找到一個為好點的 \(x\),然後做樹上差分對於每個 \(y\) 求出 \(w\) 用並查集合並即可。

然後對於每個好點就一定和 \(x\) 在同一並查集,否則無論如何也到不了 \(x\)

至於找 \(x\),就每次隨機一個點做 \(100\) 次,失敗的機率是 \(\left(\frac{4}{5}\right)^{100}\),非常小。

時間複雜度:\(O\left(100(n+m)\right)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e5 + 5;

int n, m, fl = 1;
int cnt[kMaxN], sum[kMaxN], fa[kMaxN];
bool ins[kMaxN], vis[kMaxN];
std::vector<int> G[kMaxN];

int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }

void unionn(int x, int y) {
  int fx = find(x), fy = find(y);
  if (fx != fy) fa[fx] = fy;
}

void dfs1(int u) {
  vis[u] = ins[u] = 1;
  for (auto v : G[u]) {
    if (!vis[v]) {
      dfs1(v);
    } else {
      if (!ins[v]) fl = 0;
      else ++cnt[u], --cnt[v], sum[u] += v, sum[v] -= v;
    }
  }
  ins[u] = 0;
}

void dfs2(int u) {
  ins[u] = 1;
  for (auto v : G[u]) {
    if (!ins[v]) {
      dfs2(v);
      cnt[u] += cnt[v], sum[u] += sum[v];
    } else {
    }
  }
  ins[u] = 0;
}

void dickdreamer() {
  std::cin >> n >> m;
  for (int i = 1; i <= n; ++i) G[i].clear();
  for (int i = 1; i <= m; ++i) {
    int u, v;
    std::cin >> u >> v;
    G[u].emplace_back(v);
  }
  std::vector<int> vec;
  for (int i = 1; i <= n; ++i) vec.emplace_back(i);
  std::shuffle(vec.begin(), vec.end(), std::mt19937(std::chrono::steady_clock::now().time_since_epoch().count()));
  for (int i = 0; i < std::min(100, (int)vec.size()); ++i) {
    int x = vec[i];
    for (int i = 1; i <= n; ++i) {
      fa[i] = i;
      cnt[i] = sum[i] = ins[i] = vis[i] = 0;
    }
    fl = 1, dfs1(x);
    if (!fl) continue;
    std::fill_n(ins + 1, n, 0);
    dfs2(x);
    for (int i = 1; i <= n; ++i) {
      if (cnt[i] == 1) {
        unionn(i, sum[i]);
      }
    }
    std::vector<int> idx;
    for (int i = 1; i <= n; ++i)
      if (find(i) == find(x))
        idx.emplace_back(i);
    if (idx.size() >= (n + 4) / 5) {
      for (auto u : idx) std::cout << u << ' ';
      std::cout << '\n';
    } else {
      std::cout << "-1\n";
    }
    return;
  }
  std::cout << "-1\n";
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

相關文章