Solution - Luogu P10918 小分圖最大匹配

rizynvu發表於2024-08-24

首先考慮連邊的情況。
可以把 \(a_i\) 拆成 \(\gcd(a_i, m)\times \frac{a_i}{\gcd(a_i, m)}\),因為 \(\gcd(\frac{a_i}{\gcd(a_i, m)}, m) = 1\),所以與 \(a_i\) 有連邊的 \(j\) 一定滿足 \(j\bmod \gcd(a_i, m) = 0\)

於是就可以把 \(c_{a_i}\) 放到 \(c_{\gcd(a_i, m)}\),左部點就只剩下了 \(m\) 的因數了。

考慮左部點 \(i | m, j | m\),滿足 \(i | j\),可以知道 \(j\) 所連向點 \(i\) 肯定也有連邊。
具體就是令 \(R_x\)\(x\) 與右部點有連邊的點集合,對於 \(i | j\)\(R_j\subset R_i\)

於是一個想法就是對於右部點 \(y\),與其有連邊的左部點可能有很多個,但是把其掛到左部點 \(\gcd(y, m)\) 上,然後對於倍數去建邊。
為什麼是 \(\gcd(y, m)\)?因為對於左部點 \(x | m\),若此時滿足 \(x | \gcd(y, m)\),就有 \(x | m\);同時對於 \(x \nmid \gcd(y, m)\),肯定 \(x\nmid y\)

於是接下來就考慮如何算左部點 \(x\) 掛著的右部點個數。
官方題解用了個莫反,是不是太唐了點。
實際上這個值就是 \(\varphi(\frac{m}{x})\),證明考慮選擇了 \(y\in [1, \frac{m}{x}]\)
那麼 \(\gcd(m, xy) = x\gcd(y, \frac{m}{x})\),所以當 \(\gcd(y, \frac{m}{x}) = 1\) 時滿足 \(\gcd(m, xy) = x\),這個 \(y\) 的個數顯然就是 \(\varphi(\frac{m}{x})\) 了。

那麼接下來就可以考慮網路流了。
因為此時右部點都是掛在左部點上的,所以不用建成一個二分圖的形式。
具體的,對於左部點 \(x\),連邊 \((\operatorname{S}, x, c_x), (x, \operatorname{T}, \varphi(\frac{m}{x}))\),相當於是把右部點也壓縮排來了。
同時對於左部點 \(x | y\),應該有連邊 \((x, y, \operatorname{inf})\),但這樣邊數太大了,考慮最佳化(但實際上這樣就已經可以過了)。
一個想法是對於任意一個質數 \(p\)\(x\) 的次數肯定都小於等於 \(y\) 的次數,所以若存在 \(p | y\),連邊 \((\frac{y}{p}, y, \operatorname{inf})\)

然後跑網路流即可。

複雜度 \(\mathcal{O}(\sqrt{m} + \omega(m)d(m) + \operatorname{flow}(d(m), \omega(m)d(m)))\)
其中 \(\omega(m)\) 表示 \(m\) 的質因子數量,\(d(m)\) 表示 \(m\) 的因子數量。

#include<bits/stdc++.h>
using ll = long long;
constexpr ll inf = 1e18;
const int maxN = 7e3 + 10, maxM = maxN + maxN + maxN * 12;
ll val[maxM * 2];
int to[maxM * 2], nxt[maxM * 2], fir[maxN], tot = 1;
int S, T;
inline void add(int x, int y, ll w, bool f = 1) {
   to[++tot] = y, val[tot] = w, nxt[tot] = fir[x], fir[x] = tot;
   if (f) add(y, x, 0, 0);
}
int hd[maxN], dep[maxN];
inline bool bfs() {
   for (int i = 1; i <= T; i++)
      hd[i] = fir[i], dep[i] = -1;
   dep[S] = 0;
   std::queue<int> Q; Q.push(S);
   while (! Q.empty()) {
      int u = Q.front(); Q.pop();
      if (u == T) return true;
      for (int i = hd[u]; i; i = nxt[i])
         if (dep[to[i]] == -1 && val[i])
            dep[to[i]] = dep[u] + 1, Q.push(to[i]);
   }
   return false;
}
inline ll dfs(int u, ll fl) {
   if (u == T)
      return fl;
   ll ud = 0;
   for (int &i = hd[u]; i; i = nxt[i])
      if (dep[u] + 1 == dep[to[i]] && val[i]) {
         ll k = dfs(to[i], std::min(fl - ud, val[i]));
         if (! k) dep[to[i]] = -1;
         ud += k, val[i] -= k, val[i ^ 1] += k;
         if (ud == fl)
            return fl;
      }
   return ud;
}
inline ll Dinic() {
   ll ans = 0, f;
   while (bfs())
      while ((f = dfs(S, inf)) > 0)
         ans += f;
   return ans;
}
const int maxn = 7e3 + 10;
std::map<ll, int> id;
ll res[maxn], cnt[maxn];
std::vector<ll> pr;
inline void initpr(ll m) {
   for (ll x = 2; x * x <= m; x++)
      if (m % x == 0) {
         pr.push_back(x);
         while (m % x == 0) m /= x;
      }
   if (m > 1)
      pr.push_back(m);
}
inline ll phi(ll x) {
   ll v = x;
   for (ll p : pr)
      if (x % p == 0)
         v /= p, v *= p - 1;
   return v;
}
int main() {
   int n; ll m; scanf("%d%lld", &n, &m);
   for (ll i = 1; i * i <= m; i++)
      if (m % i == 0)
         id[i] = id[m / i] = 0;
   initpr(m);
   int N = 0;
   for (auto &[x, c] : id)
      c = ++N, res[N] = x;
   for (ll a, c; n--; )
      scanf("%lld%lld", &a, &c), cnt[id[std::__gcd(a, m)]] += c;
   S = N + 1, T = N + 2;
   for (int i = 1; i <= N; i++) {
      add(S, i, cnt[i]);
      add(i, T, phi(m / res[i]));
   }
   for (int i = 1; i <= N; i++)
      for (ll p : pr)
         if (res[i] % p == 0)
            add(id[res[i] / p], i, inf);
   printf("%lld\n", Dinic());
   return 0;
}

相關文章