P10698 [SNCPC2024] 最大流 题解

Description

给定一个 \(n\) 个点 \(m\) 条边的有向无环图,图中每条边的容量为 \(1\)。对点 \(1\) 以外的每个点 \(i\),设从点 \(1\) 到点 \(i\) 的最大流为 \(f_i\),试求出 \(\min\{f_i,\ k\}\)

在边容量为 \(1\) 的图上,一个从点 \(1\) 到点 \(i\) 的流即为一条从点 \(1\) 到点 \(i\) 的路径。如果从点 \(1\) 到点 \(i\) 最多能同时有 \(f_i\) 个不交的流(即没有一条边同时属于两个流),则我们认为点 \(1\) 到点 \(i\) 的最大流是 \(f_i\)

\(n\leq 10^5,m\leq 2\times 10^5,k\leq 50\)

Solution

首先这题很像这道题,但是问题从点不交变为边不交。

我们是无法直接套用原来那道题的做法的,考虑把原图的边看成点,点看成边。

具体地,对于每个原图上的点,将其入边和出边两两连一条边,权值随机。注意到现在图里面是没有合适的起点的,由于总流量不超过 \(k\),所以可以建 \(k\) 个虚拟起点,这些起点与 \(1\) 的出边两两连边。

对于每条原图上的边 \(i\),维护 \(f_{i,j}\) 表示从第 \(j\) 个虚拟起点走到第 \(i\) 条边的权值乘积之和。那么一个点的答案就是它入边向量构成的线性基大小。

现在需要连的边可能会有 \(O(n^2)\) 级别,不能过。

这里我们会发现对于一个原图上的点,其入边的向量中不在线性基里面的是不用加入转移的,因为这里是随机变量,对于一条出边的贡献实际上是从入边向量张成线性空间里面随机选一个。只考虑线性基里面的向量的贡献跟这个的意义是一样的,所以将不在线性基里的向量删掉即可。

时间复杂度:\(O((n+m)k^2)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e5 + 5, kMaxM = 2e5 + 5, kMaxK = 55, kMod = 998244353;

int n, m, k;
int u[kMaxM], v[kMaxM], deg[kMaxN], res[kMaxN];
std::vector<int> G[kMaxN], rG[kMaxN];
std::mt19937 rnd(std::random_device{}());
std::uniform_int_distribution<> gen(0, kMod - 1);

int qpow(int bs, int64_t idx = kMod - 2) {
  int ret = 1;
  for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
    if (idx & 1)
      ret = (int64_t)ret * bs % kMod;
  return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

struct Vector {
  int a[kMaxK];
  void clear() { std::fill_n(a + 1, k, 0); }
  friend Vector operator *(Vector a, int v) {
    for (int i = 1; i <= k; ++i) a.a[i] = 1ll * v * a.a[i] % kMod;
    return a;
  }
  friend Vector operator +(Vector a, Vector b) {
    for (int i = 1; i <= k; ++i) a.a[i] = add(a.a[i], b.a[i]);
    return a;
  }
  friend Vector operator -(Vector a, Vector b) {
    for (int i = 1; i <= k; ++i) a.a[i] = sub(a.a[i], b.a[i]);
    return a;
  }
} f[kMaxM];

struct Base {
  int cnt;
  Vector a[kMaxK];
  void clear() {
    cnt = 0;
    for (int i = 1; i <= k; ++i) a[i].clear();
  }
  void ins(Vector x) {
    ++cnt;
    for (int i = 1; i <= k; ++i) {
      if (x.a[i]) {
        if (!a[i].a[i]) return void(a[i] = x);
        int d = 1ll * x.a[i] * qpow(a[i].a[i]) % kMod;
        x = x - a[i] * d;
      }
    }
    --cnt;
  }
} base;

void topo() {
  std::queue<int> q;
  for (int i = 1; i <= n; ++i)
    if (!deg[i])
      q.emplace(i);
  for (; !q.empty();) {
    int u = q.front(); q.pop();
    base.clear();
    if (u == 1) {
      for (int i = 1; i <= k; ++i) base.a[i].a[i] = 1;
    } else {
      for (auto i : rG[u]) base.ins(f[i]);
      res[u] = base.cnt;
    }
    for (auto id : G[u]) {
      int v = ::v[id];
      for (int i = 1; i <= k; ++i) f[id] = f[id] + base.a[i] * gen(rnd);
      if (!--deg[v]) q.emplace(v);
    }
  }
}

void dickdreamer() {
  std::cin >> n >> m >> k;
  for (int i = 1; i <= m; ++i) {
    std::cin >> u[i] >> v[i];
    G[u[i]].emplace_back(i), rG[v[i]].emplace_back(i);
    ++deg[v[i]];
  }
  topo();
  for (int i = 2; i <= n; ++i) std::cout << res[i] << " \n"[i == 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;
}
posted @ 2025-08-27 09:07  下蛋爷  阅读(15)  评论(0)    收藏  举报