BZOJ 2007 海拔 (对偶图 最短路)

6 篇文章 0 订阅
2 篇文章 0 订阅

BZOJ2007 [Noi2010]海拔

问题描述
YT 市是一个规划良好的城市,城市被东西向和南北向的主干道划分为 n×n个区域。简单起见,可以将 YT 市看作 一个正方形,每一个区域也可看作一个正方形。从而,YT 城市中包括(n+1)×(n+1)个交叉路口和 2n×(n+1)条双向道路(简称道路),每条双向 道路连接主干道上两个相邻的交叉路口。下图为一张 YT 市的地图(n = 2),城市被划分为 2×2 个区域,包括 3×3 个交叉路口和 12 条双向道路。
小 Z 作为该市的市长,他根据统计信息得到了每天上班高峰期间 YT 市每条道路两个方向的人流量,即在高峰期间沿 着该方向通过这条道路的人数。每一个交叉路口都有不同的海拔高度值, YT 市市民认为爬坡是一件非常累的事情,每向上爬 h 的高度,就需要消耗 h 的体力。如果 是下坡的话,则不需要耗费体力。因此如果一段道路的终点海拔减去起点海拔的值为 h(注意 h 可能是负数),那么一个人经过这段路所消耗的体力是 max{0, h}(这里 max{a, b}表示取 a, b 两个值中的较大值)。
小 Z 还测量得到这个城市西北角的交叉路口海拔为 0,东南角的交叉路口海拔为 1(如上图所示),但其它交叉路口的海拔高度都无法得知。小 Z 想知道在最理想的情况下(即你可以任意假设其他路口的海拔高度),每天上班高峰期间所有人爬坡消耗的总体力和的最小值。

Input
输入文件 altitude.in 第一行包含一个整数 n,含义如上文所示。接下来 4n(n + 1)行,每行包含一个非负整数分别表示每一条道路每一个方向的人流量信息。输入顺序:n(n + 1)个数表示所有从西到东方向的人流量,然后 n(n + 1)个数表示所有从北到南方向的人流量,n(n + 1)个数表示所有从东到西方向的人流量,最后是 n(n + 1)个数表示所有从南到北方向的人流量。对于每一个方向,输入顺序按照起点由北向南,若南北方向相同时由西到东的顺序给出(参见样例输入)。

Output
输出文件 altitude.out 仅包含一个数,表示在最理想情况下每天上班高峰期间所有人爬坡所消耗的总体力和(即总体力和的最小值),结果四舍五入到整数。

Sample Input
1
1
2
3
4
5
6
7
8

Sample Output
3

数据规模
对于 20%的数据:n ≤ 3;
对于 50%的数据:n ≤ 15;
对于 80%的数据:n ≤ 40;
对于 100%的数据:1 ≤ n ≤ 500,
0 ≤ 流量 ≤ 1,000,000且所有流量均为整数。

提示
海拔高度不一定是整数。

思路:
对偶图+dijkstra。
性质就是一定存在一组最优解,是一些点海拔为0,其余点海拔为1,且两类点各形成一个连通块,即产生代价的边为S到T的一个割边集合,代价即为流量和。
显然我们只需要考虑0和1的分界线在何处即可,就是求最小割,边集较大,所以把原图转成对偶图,然后跑dijkstra。
注意连边的时候考虑方向,我们不妨假定对偶图边经过的方向,左边海拔为0,右边海拔为1,然后只要算0到1的,所以就是正方向的权值。所以我们只需要把方向相反的两条边在对偶图中也构出方向相反的即可。

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define N 1000010
using namespace std;

int n, S, T, idc;
int head[N], dis[N], vis[N];

struct node{
    int v, nxt, w;
}ed[N << 2];

struct Node{
    int pos, dis;
    bool operator< (const Node& a) const{
        return dis > a.dis;
    }
};

priority_queue<Node> q;

void adde(int u, int v, int w){
    ed[++idc].v = v;
    ed[idc].w = w;
    ed[idc].nxt = head[u];
    head[u] = idc;
}

int Dij(){
    memset(dis, 0x7f, sizeof(dis));
    q.push((Node){S, 0});
    dis[S] = 0;
    while( !q.empty() ){
        int u = q.top().pos;
        q.pop();
        if( vis[u] ) continue;
        vis[u] = 1;
        for(int i=head[u]; i; i=ed[i].nxt)
            if(dis[ed[i].v] > dis[u] + ed[i].w){
                dis[ed[i].v] = dis[u] + ed[i].w;
                q.push((Node){ed[i].v, dis[ed[i].v]});
            }
    }
    return dis[T];
}

int id(int x, int y){
    if( !y || x == n+1 ) return S;
    if( !x || y == n+1 ) return T;
    return (x - 1) * n + y;
}

int main(){
    scanf("%d", &n);
    S = 0; T = n*n+1;//在另一条对角线上建立S,T 
    int x;
    for(int i=0; i<=n; i++) 
        for(int j=1; j<=n; j++) 
            scanf("%d", &x), adde(id(i+1,j), id(i,j), x);//每一个块抽象成一个点,S->T跑一个最短路 
    for(int i=1; i<=n; i++) 
        for(int j=0; j<=n; j++) 
            scanf("%d", &x), adde(id(i,j), id(i,j+1), x);//建图要统一方向 
    for(int i=0; i<=n; i++) 
        for(int j=1; j<=n; j++) 
            scanf("%d", &x), adde(id(i,j), id(i+1,j), x);
    for(int i=1; i<=n; i++) 
        for(int j=0; j<=n; j++) 
            scanf("%d", &x), adde(id(i,j+1), id(i,j), x);
    printf("%d", Dij());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值