「典」P5540 最小乘积相关问题

给出一个 \(n\) 个点 \(m\) 条边的无向图,第 \(i\) 条边有两个权值 \(a_i\)\(b_i\)

求该图的一棵生成树 \(T\) ,使得

\[\left(\sum_{e\in T}a_e\right)\times\left(\sum_{e\in T}b_e\right) \]

最小。

\(n\le 200,m\le 10000\)

感觉这个 trick 没见过的人真能想到?

考虑所有生成树得到的方案放在坐标系上。

相当于用一条反比例函数去切到一个最小的点。

容易发现,切到最小的点一定在左下的凸壳上,问题变为需要求出凸包上的点。

常规的求凸包做法不太行,此时有一个叫 quick-convex 的求凸包方法。

如果已知凸包上两个点 \(A,B\),那么考虑求 \(AB\) 这条线段平移之后切凸包的点,会发现一定能切到凸包的顶点 \(C\),然后 \(AC,BC\) 两条继续递归就可以得到整个凸包,容易发现凸包上的点肯定都会被遍历到。

考虑这个 \(C\) 咋求,发现 \(C\) 就是满足三角形 \(ABC\) 面积最大的点,因此直接叉积:

\[S=(C_x-A_x)(B_y-A_y)-(C_y-A_y)(B_x-A_x) \]

把和 \(C\) 有关的拿出来,可以发现就相当于最大生成树上的边权变成了:

\[a_i(B_y-A_y)+b_i(B_x-A_x) \]

这样就可以求出一个点 \(C\)

初始的 \(A,B\) 可以通过只保留一个权值得到最左边和最下面的点。

复杂度是考虑凸包上的期望点数,不太会分析,就是 \(O(能过)\)

#include <bits/stdc++.h>
#define V vector
#define Vi vector<int>
#define sz(a) ((int)a.size())
#define fi first
#define se second
#define Int pair<int, int>
#define Inf ((int)1e9)
#define pb push_back
#define ins insert
#define For(i, x, y) for (auto i = (x); i <= (y); i++)
#define Rep(i, x, y) for (auto i = (x); i >= (y); i--)
#define all(a) a.begin(), a.end()
#define IO(x) freopen(#x ".in", "r", stdin), freopen(#x ".out", "w", stdout);
using namespace std;

#define int long long

struct Edge {
  int x, y, z1, z2, v;
};

struct Ace_taffy {
  Vi f;
  Ace_taffy(int n) : f(n + 5) { For(i, 1, n) f[i] = i; }
  int get(int k) { return f[k] == k ? k : f[k] = get(f[k]); }
  void merge(int x, int y) { f[get(x)] = get(y); }
};

signed main() {
#ifndef ONLINE_JUDGE
  IO(1);
#endif
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

  int n, m;
  cin >> n >> m;

  V<Edge> a(m + 5);
  For(i, 1, m) cin >> a[i].x >> a[i].y >> a[i].z1 >> a[i].z2;

  int ans1 = Inf, ans2 = Inf;
  auto solve = [&](int x, int y) {
    For(i, 1, m) a[i].v = a[i].z1 * x + a[i].z2 * y;
    sort(&a[1], &a[m + 1], [&](Edge x, Edge y) { return x.v < y.v; });
    Ace_taffy tr(n);
    int res1 = 0, res2 = 0;
    For(i, 1, m) {
      int x = tr.get(a[i].x), y = tr.get(a[i].y);
      if (x != y) tr.merge(x, y), res1 += a[i].z1, res2 += a[i].z2;
    }
    if (res1 * res2 < ans1 * ans2 ||
        (ans1 * ans2 == res1 * res2 && res1 < ans1))
      ans1 = res1, ans2 = res2;
    return (Int){res1, res2};
  };

  auto work = [&](auto lf, Int L, Int R) -> void {
    Int M = solve(L.se - R.se, R.fi - L.fi);
    if (M == L || M == R || M.fi < L.fi || M.fi > R.fi || M.se < R.se ||
        M.se > L.se)
      return;
    lf(lf, L, M), lf(lf, M, R);
  };

  Int L = solve(1, 0), R = solve(0, 1);
  work(work, L, R);
  cout << ans1 << ' ' << ans2 << '\n';
}
posted @ 2024-03-15 22:04  houzhiyuan  阅读(39)  评论(0编辑  收藏  举报