LuoguP5540:【模板】最小乘積生成樹(幾何逼近)

題意:給定N點,M邊,每條邊有兩個屬性(a,b),如今讓你選N-1條邊出來,而後使得∑a*∑b最小。N<200,M<1e4;c++

思路:咱們把∑a當作x,∑b當作y,那麼一個方案對應一個二維座標(x,y)。假設我知道了其中兩個方案[A,B],那麼,若是另一個方案C更優,則在二維平面上,C至少要知足在A和B的左邊。而後[A,C],[C,B]繼續下推。 這個有點像凸包的逼近,因此複雜度和凸包上的點數有關,其理論點數是sqrt(lnN)的。因此總的複雜度趨近於NlogN*sqrt(lnN);spa

#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;
}
相關文章
相關標籤/搜索