最小乘积模型

最小乘积模型

冷门算法,但没想到打 SCCPC 的时候遇到了,故写个总结。

模板题 :[P5540 BalkanOI 2011] timeismoney - 洛谷

给出一个 \(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) \]

最小。

对于一个生成树,记 \(x = \sum a_i\), \(y = \sum b_i\)。可以把一个生成树用一个平面上的点 (x,y) 表示,求最小的 x * y。

容易发现,只有在突壳上的点才可能做为答案。可以画图分讨一下得到。

于是现在就是要快速找出这个突壳上的点然后更新答案。

考虑一种不是很均匀的分治。

找到最左端的点 A (即按照权值 a 跑一个生成树),和最右端的点 B (按照权值 b 跑生成树)。

找到使得 \(S\triangle ABC\) 面积最大且在直线 AB 下放的点 C,那么 C 肯定在突壳上。

${2}S\triangle ABC = \vec {AB} \times \vec {AC} = (X_B - X_A, Y_B - Y_A) \times (X_C - X_A, Y_C - Y_A) = - X_C(Y_B - Y_A) + Y_C(X_B - X_A) + $ 常数

注意,这样叉乘出来的面积根据叉乘的法则算出来的是负数,要求面积最大就是求 \(- X_C(Y_B - Y_A) + Y_C(X_B - X_A)\) 最小。

\(k1 = Y_B - Y_A\),$k2 = $ \(X_B - X_A\). 相当于对 a, b 两种边权给了个系数做最小生成树。就没了。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=202,M=1e4+4,inf=1e9;
inline ll read() {
	ll x = 0; bool f(0); char ch = getchar();
	while(!isdigit(ch)) f = (ch == '-'), ch = getchar();
	while(isdigit(ch)) x = (x<<3) + (x<<1) + (ch^48), ch = getchar();
	return f ? -x : x;
}
int n, m;
ll k1, k2;
struct Edge{
    int u, v;
    ll a, b, w;
    // inline Edge operator +=(const Edge & k) {return {u, v, a += k.a, b += k.b};} // ?
    inline bool operator < (const Edge & k) const {return w ^ k.w ? w < k.w : a < k.a;}
};
struct node{
    ll x, y;
    inline node operator +=(const Edge & k) {return (node)(x += k.a, y += k.b);} // ?
    node (int va = 0, int vb = 0) : x(va), y(vb) {}
}ans = {inf, inf};
Edge e[M];
int fa[N];
inline int find(int x) {return x ^ fa[x] ? fa[x] = find(fa[x]) : x;}
node kru() {
    node res(0, 0);
    sort(e + 1, e + m + 1);
    for(int i = 1; i <= n; ++i) fa[i] = i;
    for(int i = 1, cnt = 0; i <= m; ++i) {
        if(find(e[i].u) == find(e[i].v)) continue;
        fa[find(e[i].u)] = find(e[i].v);
        res += e[i], ++ cnt;
        if(cnt == n - 1) break;
    }
    return res;
}
inline ll calc(node a, node b, node c) {
    return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}
inline void upd(node & ans, node C) {if(ans.x * ans.y > C.x * C.y) ans = C;}
void sol(node A, node B) {
    k1 = A.y - B.y, k2 = B.x - A.x;
    for(int i = 1; i <= m; ++i) e[i].w = k1 * e[i].a + k2 * e[i].b;
    node C = kru(); 
    if(calc(A, B, C) >= 0) return ;
    upd(ans, C);
    sol(A, C), sol(C, B);
}
int main() {
	n = read(), m = read();
    for(int i = 1; i <= m; ++i) {
        int u = read() + 1, v = read() + 1;
        ll a = read(), b = read();
        e[i] = {u, v, a, b, a};
    }
    node A = kru(); ans = A;
    for(int i = 1; i <= m; ++i) e[i].w = e[i].b;
    node B = kru(); upd(ans, B);
    sol(A, B);
    printf("%lld %lld\n", ans.x, ans.y);
	return 0;
} 
posted @ 2025-06-09 21:14  花子の水晶植轮daisuki  阅读(29)  评论(0)    收藏  举报
https://blog-static.cnblogs.com/files/zouwangblog/mouse-click.js