【最小乘积生成树】BZOJ2395[Balkan 2011] Timeismoney

【题目】
原题地址
n n 个城市(编号从0..n1), m m 条公路(双向的),从中选择n1条边,使得任意的两个城市能够连通,一条边需要的 c c 的费用和t的时间,定义一个方案的权值 v=(n1 v = ( n − 1 条边的费用和) (n1条边的时间和),你的任务是求一个方案使得 v v 最小

【题目分析&学习】
假装自己学了一波东西:最小乘积生成树。
以下转自:dalao的BLOG

最小乘积生成树定义

有一张n个点 m m 条边的无向图,每条边有k个权值。
现在要取一个边集M使得其将所有点连通,并使
ki=1(jMjcost(j,vali)) ∏ i = 1 k ( ∑ j j ∈ M c o s t ( j , v a l i ) ) 最小
即个边集的每一种边权的总和的乘积最小。

比如:
k=1 k = 1 时,就是裸最小生成树。
k=2 k = 2 时,就是要使 [边集的权值1的和]*[边集的权值2的和] 最小。

最小乘积生成树的一种求法:

广义上的说法:
首先我们可以把每种生成树想成一个 k k 维的点,第i维的坐标即那一维上权值的和。
然后我们可以先求出每一维坐标最小的一棵生成树(裸上最小生成树就好),
然后得到一个 k1 k − 1 维的面,然后我们来求一下离这个面最远的点,然后分治下去……据说期望很快……

二维最小乘积生成树的求法:

给每一棵生成树都定义两个权值 XY X 、 Y ,其中 X X 为其包含的所有边的权值x的和, Y Y 为其包含的所有边的权值y的和,那么我们可以把每一种生成树看成一个坐标。
我们先求出坐标 x x 最小的一棵生成树,再求出坐标y最小的一棵生成树。
然后我们可以考虑,最优的点一定在下凸包上【证明一】,然后我们要进行一个不断向左下拓展点的过程:对于两个点 AB A 、 B 形成的直线,我们可以找出在这条直线左下的最远的点 C C ,然后对ACCB递归做同样的过程,直到找不到一个在左下的点 C C 为止。

然后如何找一个最远的点C呢?——由于C离AB最远,所以S△ABC面积最大。就是让AB×AC最小

AB×AC=(B.xA.x)(C.yA.y)(B.yA.y)(C.xA.x)=C.x(A.yB.y)+C.y(B.xA.x)+(...)(3)(4) (3) A B → × A C → = ( B . x − A . x ) ∗ ( C . y − A . y ) − ( B . y − A . y ) ∗ ( C . x − A . x ) (4) = C . x ∗ ( A . y − B . y ) + C . y ∗ ( B . x − A . x ) + ( . . . )

(...) ( . . . ) 是只与 A,B A , B 有关的量,可以看做常数。
因此我们将所有边权设为 a[k].x(A.yB.y)+a[k].y(B.xA.x) a [ k ] . x ∗ ( A . y − B . y ) + a [ k ] . y ∗ ( B . x − A . x ) ,然后求最小生成树就行了。
边界 AB×AC0 A B → × A C → ≥ 0

【证明一】
每个点 (xi,yi) ( x i , y i ) 都对应一条函数曲线 ki=xi?yi k i = x i ? y i ,而任意两不同 ki k i ,它们的函数曲线是不交的(有交的话则存在一点 (xj,yj) ( x j , y j ) 使得 ki=xj?yj=kj k i = x j ? y j = k j ki!=kj k i ! = k j 成立,显然这是悖论),那么显然最优点肯定不会在凸包内,否则必有凸包上一点比它优。
那么会不会求出这个某种意义上的凸包后,最优点在凸包外,却没被找到呢?
不会。
若有这种情况,此点必然在凸包上某边的左下方,然后一定会被找出来。。

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int INF=1e9;
const int N=205;
const int M=1e5+5;
int n,m;
int fa[N];

struct Tway
{
    int x,y,c,t;
    LL v;
};
Tway e[M];

struct Tpoint
{
    LL x,y;
};
Tpoint ans,A,B;

bool cmp(Tway X,Tway Y)
{
    return X.v<Y.v;
}

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

Tpoint Better(Tpoint X,Tpoint Y)
{
    if((X.x*X.y==Y.x*Y.y && X.x>Y.x) || X.x*X.y>Y.x*Y.y)
        return Y;
    return X;
}

Tpoint Kruscal()
{
    Tpoint O=(Tpoint){0,0};
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=n;++i)   
        fa[i]=i;
    int now=1;
    for(int i=1;i<=m;++i)
    {
        int fx=findf(e[i].x),fy=findf(e[i].y);
        if(fx==fy)
            continue;
        fa[fx]=fy;
        O.x+=e[i].c;O.y+=e[i].t;++now;
        if(now==n)
            break;
    }
    ans=Better(ans,O);
    return O;
}

LL cross(Tpoint a,Tpoint b,Tpoint c)
{
    return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}

void solve(Tpoint X,Tpoint Y)
{
    LL tx=Y.x-X.x,ty=X.y-Y.y;
    for(int i=1;i<=m;++i)
        e[i].v=1ll*e[i].c*ty+1ll*e[i].t*tx;
    Tpoint O=Kruscal();
    if(cross(O,X,Y)>=0)
        return;
    solve(X,O);
    solve(O,Y);
}

int main()
{
//  freopen("BZOJ2395.in","r",stdin);
//  freopen("BZOJ2395.out","w",stdout);

    ans.x=ans.y=INF;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].c,&e[i].t);
        ++e[i].x;++e[i].y;e[i].v=e[i].c;
    }
    A=Kruscal();
    for(int i=1;i<=m;++i)
        e[i].v=e[i].t;
    B=Kruscal();
    solve(A,B);
    printf("%lld %lld\n",ans.x,ans.y);

    return 0;
}

不知道高维的情况应该怎样写呢?貌似还没有这种题出来呢。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值