P5540

给出一个 nn 个点 mm 条边的无向图,第 ii 条边有两个权值 aia_ibib_i

求该图的一棵生成树 TT ,使得

(eTae)×(eTbe)\left(\sum_{e\in T}a_e\right)\times\left(\sum_{e\in T}b_e\right)

最小。

n200,m10000n\le 200,m\le 10000

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

考虑所有生成树得到的方案放在坐标系上,即 (ae,be)(\sum a_e,\sum b_e) 为一个点。

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

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

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

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

注意到这个算法的本质和 wqs 二分是一样的,就是选一条直线去切凸包,只不过这个是要求出凸包上的所有的点,正常 wqs 二分做也是可以的,只不过会多一个 log\log

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

S=(CxAx)(ByAy)(CyAy)(BxAx)S=(C_x-A_x)(B_y-A_y)-(C_y-A_y)(B_x-A_x)

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

ai(ByAy)+bi(BxAx)a_i(B_y-A_y)+b_i(B_x-A_x)

这样就可以求出一个点 CC

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

复杂度是凸包上的点数乘以每次求最小生成树的复杂度,不太会分析,就是 O(能过)O(能过)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#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';
}