• bzoj2395 [Balkan 2011]Timeismoney


    题意:每条边有两个权值a,b,求图的最小二元和乘积生成树(即该树的sum_a*sum_b最小)。

    标程:

     1 #include<bits/stdc++.h>
     2 #define P pair<ll,ll>
     3 #define fir first
     4 #define sec second
     5 using namespace std;
     6 typedef long long ll;
     7 int read()
     8 {
     9    int x=0,f=1;char ch=getchar();
    10    while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
    11    while (ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    12    return x*f;
    13 }
    14 const int N=10005;
    15 int f[N],n,m; 
    16 ll ans,qa,qb;
    17 struct node{ll u,v,a,b,w;}e[N];
    18 int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
    19 bool cmp_a(const node &A,const node &B){return A.a<B.a;}
    20 bool cmp_b(const node &A,const node &B){return A.b<B.b;}
    21 bool cmp_w(const node &A,const node &B){return A.w<B.w;}
    22 ll chaji(P A,P B,P C){return (B.fir-A.fir)*(C.sec-A.sec)-(B.sec-A.sec)*(C.fir-A.fir);}
    23 P mst()
    24 {
    25     ll s1=0,s2=0;
    26     for (int i=1;i<=n;i++) f[i]=i;
    27     for (int i=1;i<=m;i++)
    28       if (find(e[i].u)!=find(e[i].v))
    29         f[find(e[i].u)]=find(e[i].v),s1+=e[i].a,s2+=e[i].b;
    30     ll t=s1*s2;
    31     if (t<ans) ans=t,qa=s1,qb=s2;
    32       else if (t==ans&&s1<qa) qa=s1,qb=s2;
    33     return P(s1,s2);
    34 }
    35 void solve(P A,P B)
    36 {
    37    for (int i=1;i<=m;i++)
    38      e[i].w=(B.fir-A.fir)*e[i].b-(B.sec-A.sec)*e[i].a;
    39    sort(e+1,e+m+1,cmp_w);P C=mst();
    40     if (chaji(A,B,C)>=0) return;
    41     solve(A,C);solve(C,B);
    42 }
    43 int main()
    44 {
    45     n=read();m=read();ans=1ll<<60;
    46     for (int i=1;i<=m;i++) e[i].u=read()+1,e[i].v=read()+1,e[i].a=read(),e[i].b=read();
    47     sort(e+1,e+m+1,cmp_a);P A=mst();
    48     sort(e+1,e+m+1,cmp_b);P B=mst();
    49     solve(A,B);
    50     printf("%lld %lld
    ",qa,qb);
    51    return 0;
    52 }
    View Code

    题解:数形结合+凸包

    把每棵生成树按照(sum_a,sum_b)映射到坐标系上,基于乘积的反比例函数性质,动态维护下凸包。

    具体地,先找到sum_a最小的点A和sum_b最小的点B,连边,在这条线的下方找到一个距离这条直线最远的点C。那么△ABC中的点一定不及三个端点的答案小。继续AC连边和CB连边找下方点,分治。并对端点统计。

    难点在于最远点怎么求?转换成三角形面积最大,用叉积表示,其中一条边已知。向量AB叉乘向量AC=(B.x-A.x)*C.y-(B.y-A.y)*C.x-A.y*(B.x-A.x)+A.x*(B.y-A.y)(这是负的,使其最小)前两项和C有关,后两项都已知,重新对边赋权为(B.x-A.x)*e[i].b-(B.y-A.y)*e[i].a,跑mst,即为C点。

    最坏复杂度O(nmlogm)。

    如果是三元,那么就相当于在平面下找最远点,使得中间体积最大。分割的时候分成三个部分。

  • 相关阅读:
    Cufon css3@font-face
    HTML5 Canvas
    HTML5 Canvas 的宽高
    :nth-child()
    new Image()
    ios有些机型input和fixed导致的页面错位问题
    使用performance进行前端性能监控
    throttle(节流)和debounce(防抖)
    object-fit/object-position
    flex布局与ellipsis冲突问题
  • 原文地址:https://www.cnblogs.com/Scx117/p/9179251.html
Copyright © 2020-2023  润新知