最小乘积生成树

感觉上次写知识点已经是若干年前了。

板子是 P5540。

把生成树的 \(\sum a,\sum b\) 看做坐标 \((x,y)\) 扔到二维平面上,那么我们就相当于找一个 \(x\times y\) 最小的点。这个点显然在凸包上。当然我们不可能把所有点找出来跑凸包。那我们想办法只扫可能成为答案的点,即只找一个凸壳:

  1. 找到和 \(x\) 轴、\(y\) 轴最近的点。这个简单,可以直接以 \(a,b\) 为边权分别跑一遍凸包。设以 \(a\) 为边权得到点 \(A\),以 \(b\) 为边权得到点 \(B\)

  2. 找到 \(AB\) 左下方且距离 \(AB\) 最远的点 \(C\)。最远可以变成 \(S_{\triangle ABC}\) 最大。那么 \(S_{\triangle ABC}=-\frac{\overrightarrow{AB}\times\overrightarrow{AC}}2\),最小化 \(\overrightarrow{AB}\times\overrightarrow{AC}\) 即可。

    化一下式子:

    \[\begin{aligned} \overrightarrow{AB}\times\overrightarrow{AC}=&(x_B-x_A)(y_C-y_A)-(x_C-x_A)(y_B-y_A)\\ =&(x_B-x_A)y_C+(y_A-y_B)x_C-(x_B-x_A)y_A+(y_B-y_A)x_A \end{aligned} \]

    最小化前两项,可以直接把边权设为 \((x_B-x_A)b_i+(y_A-y_B)a_i\) 跑最小生成树。

  3. 回到第二步递归向下分治处理。如果得到的 \(C\)\(AB\) 上边说明 \(C\) 不在凸包上,返回即可。

\(n\) 个点的凸包期望大小是 \(O(\sqrt{\ln n})\) 的,所以复杂度不会证。

upd:现在会了,但是不想写。见 2017 集训队论文《正多边形》命题报告。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
int n,m;
struct gra{
    int u,v,w,a,b;
    bool operator<(const gra&s)const{
        return w<s.w;
    }
}edge[10010];
struct node{
    int x,y;
    node operator+(const node&s)const{
        return {x+s.x,y+s.y};
    }
    node operator-(const node&s)const{
        return {x-s.x,y-s.y};
    }
    int operator^(const node&s)const{
        return x*s.y-y*s.x;
    }
}ans;
int fa[210];
int find(int x){
    return x==fa[x]?fa[x]:fa[x]=find(fa[x]);
}
void merge(int x,int y){
    fa[find(y)]=find(x);
}
node kruskal(){
    node x={};
    for(int i=1;i<=n;i++)fa[i]=i;
    sort(edge+1,edge+m+1);
    for(int i=1;i<=m;i++){
        if(find(edge[i].u)!=find(edge[i].v)){
            merge(edge[i].u,edge[i].v);
            x=x+(node){edge[i].a,edge[i].b};
        }
    }
    long long ret=1ll*ans.x*ans.y,tmp=1ll*x.x*x.y;
    if(tmp<ret||(tmp==ret&&x.x<ans.x))ans=x;
    return x;
}
void solve(node x,node y){
    for(int i=1;i<=m;i++)edge[i].w=edge[i].b*(y.x-x.x)+edge[i].a*(x.y-y.y);
    node ret=kruskal();
    if(((y-x)^(ret-x))>=0)return;
    solve(x,ret);solve(ret,y);
}
int main(){
    scanf("%d%d",&n,&m);ans.x=ans.y=0x3f3f3f3f;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d%d",&edge[i].u,&edge[i].v,&edge[i].a,&edge[i].b);
        edge[i].u++;edge[i].v++;
    }
    for(int i=1;i<=m;i++)edge[i].w=edge[i].a;
    node x=kruskal();
    for(int i=1;i<=m;i++)edge[i].w=edge[i].b;
    node y=kruskal();
    solve(x,y);
    printf("%d %d\n",ans.x,ans.y);
    return 0;
}

另一个题是[HNOI2014]画框,把最小生成树换成费用流就行了。

posted @ 2023-04-19 17:45  gtm1514  阅读(24)  评论(0编辑  收藏  举报