BZOJ2395: [Balkan 2011]Timeismoney

乘积规划…神犇称其为隐式自适应凸包…
设每棵生成树为坐标系上的一个点,sigma(x[i])为横坐标,sigma(y[i])为纵坐标。则问题转化为求一个点,使得xy=k最小。
注意到这是一个反比例函数。所以显然的,有可能成为最优解的点集是一个凸包。
首先找到x最小的点A与y最小的点B,再找到离AB最远的点C,形成了一个三角形,这个三角形内部的点不可能成为最优答案(最优解的点集是一个凸包),所以我们继续递归AC,CB即可,对于找到的每一个点C尝试更新答案即可。
photo
怎么找到离一条直线最远的点呢?因为C离AB最远,所以S△ABC面积最大。即向量AB与向量AC的叉积最小(因为叉积是有向面积,是负数,所以取最小)
最小化:(B.x-A.x)(C.y-A.y)-(B.y-A.y)(C.x-A.x)
=(B.x-A.x)C.y+(A.y-B.y)*C.x - A.y(B.x-A.x)+A.x(B.y-A.y)
后面部分是常数,也就是最小化前面的部分。
将每条边的边权e[i].w设为e[i].t*(a.y-b.y)+e[i].c*(b.x-a.x);跑一遍最小生成树即可。
当叉积为正时,就是找不到一个那样的C点,return即可
有一个小技巧,kruskal记录下当前已经合并了的联通块的个数,是个小常数剪枝。还是比较有用的。
复杂度我也不知道…>_<…因为凸包上的点不会很多,所以速度应该还不错…如果谁能证明求告知…

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
//by:MirrorGray
using namespace std;
const int N=211111; 
int n,m,fa[N];
struct P{
    int x,y;
    P(int a=0,int b=0){x=a;y=b;}
    P operator -(const P&b){
        return P(x-b.x,y-b.y);
    }
    friend int X(const P&a,const P&b){
        return a.x*b.y-a.y*b.x;
    }
    bool operator <(const P&b)const{
        if(x*y!=b.x*b.y)return x*y<b.x*b.y;
        return x<b.x;
    }
    void op(){
        printf("%d %d\n",x,y);
    }
}ans;
struct edge{
    int a,b,t,c,w;
    void read(){
        scanf("%d%d%d%d",&a,&b,&t,&c);
        a++;b++;
    }
    bool operator <(const edge&b)const{
        return w<b.w;
    }
}e[N];

int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}

P kruskal(){
    P ret;int tmp=0;
    sort(e+1,e+1+m);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m&&tmp<n-1;i++){
        int fx=find(e[i].a),fy=find(e[i].b);
        if(fx==fy)continue;
        tmp++;
        fa[fx]=fy;ret.x+=e[i].t;ret.y+=e[i].c;
    }
    ans=min(ans,ret);
    return ret;
}

void solve(P a,P b){
    for(int i=1;i<=m;i++)e[i].w=e[i].t*(a.y-b.y)+e[i].c*(b.x-a.x);
    P c=kruskal();
    if(X(b-a,c-a)>=0)return ;
    solve(a,c);solve(c,b);
}

int main(){
    scanf("%d%d",&n,&m);
    ans=P(40000,40000);
    for(int i=1;i<=m;i++)e[i].read();
    for(int i=1;i<=m;i++)e[i].w=e[i].t;
    P t1=kruskal();
    for(int i=1;i<=m;i++)e[i].w=e[i].c;
    P t2=kruskal();
    solve(t1,t2);
    ans.op();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值