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!
posted @ 2019-09-28 09:59  nimphy  阅读(231)  评论(0编辑  收藏  举报