problem
给出有 n n n 个点, m m m 条边的无向连通图,每条边有两个参数 c c c 和 t t t,现要求出一个生成树,使得边的 ∑ c × ∑ t \sum c\times \sum t ∑c×∑t 最小,输出这种情况下的 ∑ c \sum c ∑c 和 ∑ t \sum t ∑t(当有多个方案是输出 ∑ c \sum c ∑c 更小的那个)。
数据范围: 1 ≤ n ≤ 200 1\le n\le200 1≤n≤200, 1 ≤ m ≤ 10000 1\le m\le10000 1≤m≤10000, 0 ≤ t , c ≤ 255 0\le t,c\le255 0≤t,c≤255。
solution
最小乘积生成树的板子题啦。
想学习的话可以参考这篇博客,写的比较详细。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
const int N=205,M=10005;
int n,m,fa[N];
struct point{
int x,y;
point(){}
point(int x,int y):x(x),y(y){}
friend point operator+(const point &a,const point &b) {return point(a.x+b.x,a.y+b.y);}
friend point operator-(const point &a,const point &b) {return point(a.x-b.x,a.y-b.y);}
friend int dot(const point &a,const point &b) {return a.x*b.x+a.y*b.y;}
friend int cross(const point &a,const point &b) {return a.x*b.y-a.y*b.x;}
friend ll calc(const point &a) {return (ll)a.x*a.y;}
};
struct edge{int u,v,c,t,w;}e[M];
point ans=point(inf,inf),Minc,Mint;
bool operator<(const edge &a,const edge &b) {return a.w<b.w;}
int find(int x) {return x==fa[x]?x:fa[x]=find(fa[x]);}
point Kruskal(){
sort(e+1,e+m+1);
point now=point(0,0);
for(int i=1;i<=n;++i) fa[i]=i;
for(int i=1;i<=m;++i){
int x=find(e[i].u),y=find(e[i].v);
if(x!=y) fa[x]=y,now=now+point(e[i].c,e[i].t);
}
if(calc(ans)>calc(now)||(calc(ans)==calc(now)&&ans.x>now.x)) ans=now;
return now;
}
void solve(point A,point B){
for(int i=1;i<=m;++i)
e[i].w=e[i].c*(A.y-B.y)+e[i].t*(B.x-A.x);
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(int i=1;i<=m;++i) scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].c,&e[i].t),++e[i].u,++e[i].v;
for(int i=1;i<=m;++i) e[i].w=e[i].c;Minc=Kruskal();
for(int i=1;i<=m;++i) e[i].w=e[i].t;Mint=Kruskal();
solve(Minc,Mint);
printf("%d %d\n",ans.x,ans.y);
return 0;
}