嘘~ 正在从服务器偷取页面 . . .

洛谷P5540 [BalkanOI2011] timeismoney | 最小乘积生成树 题解


洛谷P5540 [BalkanOI2011] timeismoney | 最小乘积生成树 题解

题目链接:P5540 [BalkanOI2011] timeismoney | 最小乘积生成树

题意:给出一个 $n$ 个点 $m$ 条边的无向图,第 $i$ 条边有两个权值 $a_i$ 和 $b_i$ 。

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

最小

注意到对于一棵生成树,式子中的左右两边均为常数

考虑将生成树映射到平面上的点 $(x,y)$

其中 $x=\sum\limits_{e\in T}a_e,y=\sum\limits_{e\in T}b_e$

那么问题就转化为了求一个点 $(x,y)$ 使得 $x\times y$ 最小

可以发现这些点的分布存在边界

即 $x$ 特别大 $y$ 特别小的时候以及 $y$ 特别大 $x$ 特别小的时候

这两种情况分别对应点 $A$ 和 $B$

则有 $A$ 为距离 $x$ 轴最近的点, $B$ 为距离 $y$ 轴最近的点

对于直线 $AB$ ,显然在 $AB$ 上方的不是优解

根据反比例函数的性质,可以发现这个解一定在以 $A,B$ 为端点的下凸包上

由于我们不可能枚举出所有的生成树然后求二维凸包

考虑递归求解,不知道大家知不知道割圆术,不知道也没关系

这个解法有点像割圆术

首先找出距离直线 $AB$ 最远的点 $C$ (在 $AB$ 下方)

然后递归 $AC$ 和 $CB$ ,即可求出解

如何寻找 $C$ 点呢?可以发现此时 $S_{\triangle ABC}$ 取到了最大值

而 $S_{\triangle ABC} = -\dfrac{1}{2}\overset{\longrightarrow}{AB}\times \overset{\longrightarrow}{AC}$

(注:这里写的不是很规范,其实应该除以一个单位向量 $\boldsymbol{z}$ 的,不重要不管了

推柿子环节 qwq

可以发现后面两项是定值

因此我们将边权设为 $(x_B-x_A)b_i+(y_A-y_B)a_i$ ,然后跑最小生成树即可

由于 $n$ 很小,且凸包上的期望点数分析极其复杂

本题复杂度可以近似看作 $O(km\log m)$,其中 $k$ 为一个小常数,据说是 $\sqrt{\ln n}$ ,不太清楚

代码如下

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define gc() readchar()
#define pc(a) putchar(a)
#define SIZ (int)(1e5+15)
char buf1[SIZ],*p1,*p2;
char readchar()
{
    if(p1==p2)p1=buf1,p2=buf1+fread(buf1,1,SIZ,stdin);
    return p1==p2?EOF:*p1++;
}
template<typename T>void read(T &k)
{
    char ch=gc();T x=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
    k=x*f;
}
template<typename T>void write(T k)
{
    if(k<0){k=-k;pc('-');}
    static T stk[66];T top=0;
    do{stk[top++]=k%10,k/=10;}while(k);
    while(top){pc(stk[--top]+'0');}
}
#define N (int)(2e2+15)
#define M (int)(1e4+15)
int n,m;
struct Edge
{
    int u,v,a,b,w,next;
}e[M];
struct vct
{
    int x,y;
}ans;
int f[N];
vct operator-(vct a,vct b){return (vct){a.x-b.x,a.y-b.y};}
int cross(vct a,vct b){return a.x*b.y-a.y*b.x;}
void init(){for(int i=1; i<=n; i++) f[i]=i;}
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void merge(int u,int v){u=find(u);v=find(v);f[u]=v;}
vct kruskal()
{
    sort(e+1,e+1+m,[](Edge a,Edge b){
        return a.w<b.w;
    });
    init();
    vct res={0,0};
    int cnt=0;
    for(int i=1; i<=m; i++)
    {
        int u=find(e[i].u),v=find(e[i].v);
        if(u!=v)
        {
            merge(u,v);
            res.x+=e[i].a;
            res.y+=e[i].b;
            if(++cnt==n-1)break;
        }
    }
    int Ans=ans.x*ans.y,Res=res.x*res.y;
    if(Ans>Res||Ans==Res&&ans.x>res.x)ans=res;
    return res;
}
void solve(vct A,vct B)
{
    for(int i=1; i<=m; i++)
        e[i].w=e[i].b*(B.x-A.x)+e[i].a*(A.y-B.y);
    vct C=kruskal();
    if(cross(B-A,C-A)>=0)return;
    solve(A,C);solve(C,B);

}
signed main()
{
    read(n);read(m);
    ans={0x3f3f3f3f,0x3f3f3f3f};
    for(int i=1; i<=m; i++)
    {
        read(e[i].u);read(e[i].v);
        read(e[i].a);read(e[i].b);
        e[i].u++;e[i].v++;
    }
    for(int i=1; i<=m; i++)
        e[i].w=e[i].a;
    vct A=kruskal();
    for(int i=1; i<=m; i++)
        e[i].w=e[i].b;
    vct B=kruskal();
    solve(A,B);
    printf("%lld %lld\n",ans.x,ans.y);
    return 0;
}

文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录