最小乘积生成树
感觉上次写知识点已经是若干年前了。
板子是 P5540。
把生成树的 \(\sum a,\sum b\) 看做坐标 \((x,y)\) 扔到二维平面上,那么我们就相当于找一个 \(x\times y\) 最小的点。这个点显然在凸包上。当然我们不可能把所有点找出来跑凸包。那我们想办法只扫可能成为答案的点,即只找一个凸壳:
-
找到和 \(x\) 轴、\(y\) 轴最近的点。这个简单,可以直接以 \(a,b\) 为边权分别跑一遍凸包。设以 \(a\) 为边权得到点 \(A\),以 \(b\) 为边权得到点 \(B\)。
-
找到 \(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\) 跑最小生成树。
-
回到第二步递归向下分治处理。如果得到的 \(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]画框,把最小生成树换成费用流就行了。
快踩