LuoguP5540:【模板】最小乘积生成树(几何逼近)
题意:给定N点,M边,每条边有两个属性(a,b),现在让你选N-1条边出来,然后使得∑a*∑b最小。N<200,M<1e4;
思路:我们把∑a看成x,∑b看成y,那么一个方案对应一个二维坐标(x,y)。假设我知道了其中两个方案[A,B],那么,如果另外一个方案C更优,则在二维平面上,C至少要满足在A和B的左边。然后[A,C],[C,B]继续下推。 这个有点像凸包的逼近,所以复杂度和凸包上的点数有关,其理论点数是sqrt(lnN)的。所以总的复杂度趋近于NlogN*sqrt(lnN);
#include<bits/stdc++.h> #define ll long long #define pii pair<ll,ll> #define f first #define ss second #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=2000010; struct in{ int u,v;ll a,b,C; }s[maxn]; bool cmp(in p,in q){ return p.C<q.C;} int fa[maxn],N,M; ll ans=1LL<<60; pii fcy; int find(int x){ if(x==fa[x]) return x; return fa[x]=find(fa[x]); } pii solve() { pii res=make_pair(0,0); rep(i,1,N) fa[i]=i; sort(s+1,s+M+1,cmp); rep(i,1,M) { if(find(s[i].u)==find(s[i].v)) continue; fa[find(s[i].u)]=find(s[i].v); res.f+=s[i].a; res.ss+=s[i].b; } if(res.f*res.ss<ans||(res.f*res.ss==ans&&res.f<fcy.f)) ans=res.f*res.ss,fcy=res; return res; } void MinMul(pii A,pii B) { pii C; rep(i,1,M) s[i].C=(B.f-A.f)*s[i].b+(A.ss-B.ss)*s[i].a; C=solve(); if(1LL*(B.f-A.f)*(C.ss-A.ss)-1LL*(B.ss-A.ss)*(C.f-A.f)>=0) return ; MinMul(A,C); MinMul(C,B); } int main() { pii A,B; scanf("%d%d",&N,&M); rep(i,1,M) { scanf("%d%d%lld%lld",&s[i].u,&s[i].v,&s[i].a,&s[i].b); s[i].u++; s[i].v++; } rep(i,1,M) s[i].C=s[i].a; A=solve(); rep(i,1,M) s[i].C=s[i].b; B=solve(); MinMul(A,B); printf("%lld %lld\n",fcy.f,fcy.ss); return 0; }
It is your time to fight!