「典」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';
}