• Luogu5540 最小乘积生成树


    Luogu5540 最小乘积生成树

    题目链接:洛谷

    题目描述:对于一个(n)个点(m)条边的无向连通图,每条边有两个边权(a_i,b_i),求使((sum a_i) imes (sum b_i))最小的生成树。

    数据范围:(nle 200,mle 10000,a_i,b_ile 255)

    这题是一道非常妙的计算几何题目。

    我们对于每个生成树,用((sum a_i,sum b_i))这个二维平面上的点来表示它,那么就是求所有点中横坐标乘纵坐标的最小值。

    画画图就可以发现,答案只有可能在下凸包上,为什么呢?

    因为如果(C)在线段(AB)上方,其中(x_Ay_A=x_By_B),因为反比例函数下凸,所以(x_Cy_C>x_Ay_A)

    但是生成树可能有很多个,怎么得到下凸包上的点呢?

    Step1 求最靠近(x,y)轴的两个点(A,B)

    为什么呢?因为(A,B)两个点必定在下凸包上面。令(w_i=a_i)(b_i)用最小生成树求(A,B)

    Step2 求(C)在直线(AB)下方且(S_{Delta ABC})最大

    为什么呢?因为(C)点必定在下凸包上面,否则它不是最大的点。我们发现

    [egin{aligned} -frac{1}{2}S_{Delta ABC}&=overrightarrow{AB} imes overrightarrow{AC} \ &=(x_B-x_A)(y_C-y_A)-(y_B-y_A)(x_C-x_A) \ &=((x_A-x_B)y_A-(y_A-y_B)x_A)+(x_B-x_A)y_C-(y_B-y_A)x_C end{aligned} ]

    (w_i=(x_B-x_A)b_i-(y_B-y_A)a_i)就会得到(C)

    Step3 将(A,C)和(C,B)代入Step2递归

    这样就可以求出下凸包上所有的点。那什么时候终止递归呢?当然就是(C)不存在,或者说求出的(C)(AB)上方。

    时间复杂度为(O(kmlog m)),其中(k)为下凸包上点的个数,在随机数据下不会很大。

    #include<bits/stdc++.h>
    #define Rint register int
    using namespace std;
    typedef long long LL;
    const int N = 10003;
    int n, m, a[N], b[N], fa[N];
    struct Point {
    	int x, y;
    	inline Point(int _x = 0, int _y = 0): x(_x), y(_y){}
    	inline Point operator - (const Point &o) const {return (Point){x - o.x, y - o.y};}
    } A, B, ans(1e9, 1e9);
    inline LL cross(Point a, Point b){return (LL) a.x * b.y - (LL) b.x * a.y;}
    struct Edge {
    	int u, v, w, id;
    	inline bool operator < (const Edge &o) const {return w < o.w;}
    } e[N];
    inline int getfa(int x){return x == fa[x] ? x : fa[x] = getfa(fa[x]);}
    inline Point Kruskal(){
    	Point res; sort(e + 1, e + m + 1);
    	for(Rint i = 1;i <= n;i ++) fa[i] = i;
    	for(Rint i = 1, p = 1;i <= m && p < n;i ++){
    		int u = getfa(e[i].u), v = getfa(e[i].v);
    		if(u != v){fa[u] = v; ++ p; res.x += a[e[i].id]; res.y += b[e[i].id];}
    	}
    	LL Ans = (LL) ans.x * ans.y, Res = (LL) res.x * res.y;
    	if(Ans > Res || Ans == Res && ans.x > res.x) ans = res;
    	return res;
    }
    inline void solve(Point A, Point B){
    	for(Rint i = 1;i <= m;i ++) e[i].w = (A.y - B.y) * a[e[i].id] - (A.x - B.x) * b[e[i].id]; Point C = Kruskal();
    	if(cross(B - A, C - A) >= 0) return;
    	solve(A, C); solve(C, B);
    }
    int main(){
    	scanf("%d%d", &n, &m);
    	for(Rint i = 1;i <= m;i ++) scanf("%d%d%d%d", &e[i].u, &e[i].v, a + i, b + i), ++ e[i].u, ++ e[i].v, e[i].id = i;
    	for(Rint i = 1;i <= m;i ++) e[i].w = a[e[i].id]; Point A = Kruskal();
    	for(Rint i = 1;i <= m;i ++) e[i].w = b[e[i].id]; Point B = Kruskal();
    	solve(A, B); printf("%d %d", ans.x, ans.y);
    }
    
  • 相关阅读:
    一个面试问题的答案总结
    全局变量与局部变量的特点
    浮点数类型在内存当中是如何存储的
    常用的几种调用约定
    裸函数
    安卓活动的启动模式
    安卓的生命周期
    android中的内部存储与外部存储
    堆栈图学习汇编结束篇最后一个堆栈图的练习
    Android内部存储与外部存储的文件操作类
  • 原文地址:https://www.cnblogs.com/AThousandMoons/p/11519498.html
Copyright © 2020-2023  润新知