NOI2010 海拔

问题描述


小 Z 作为该市的市长,他根据统计信息得到了每天上班高峰期间 YT 市每条道路两个方向的人流量,即在高峰期间沿 着该方向通过这条道路的人数。每一个交叉路口都有不同的海拔高度值,YT 市市民认为爬坡是一件非常累的事情,每向上爬 h 的高度,就需要消耗 h的体力。如果 是下坡的话,则不需要耗费体力。因此如果一段道路的终点海拔减去起点海拔的值为 h(注意 h 可能是负数),那么一个人经过这段路所消耗的体力是 max{0, h}(这里max{a, b}表示取 a, b 两个值中的较大值)。小 Z 还测量得到这个城市西北角的交叉路口海拔为 0,东南角的交叉路口海拔为1(如上图所示),但其它交叉路口的海拔高度都无法得知。小 Z 想知道在最理想的情况下(即你可以任意假设其他路口的海拔高度),每天上班高峰期间所有人爬坡消耗的总体力和的最小值。

输入格式

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

输出格式

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

样例输入

1
1
2
3
4
5
6
7
8

样例输出

3

这里写图片描述

找出图中海拔最高的点,如果它不是起止点,那么这条路径必定是先上坡再下坡,这样就会浪费了体力,我们可以将最高点下调至他两边较高的点。那么你可以发现,最高点一定是终点,而最低点就是起始点。

还有一个问题是小数,其实小数的问题很好解决,可以把小数向上或向下调整到1或0使得代价不增。

那么当你完全想到上面两点时,你会发现为0的点和为1的点构成两个相通的集合时,体力消耗最少值就是就是连接这两个集合的所有边权之和。

那么就可以想到

最小割

但是点数是n2的。不能用最大流。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define maxn 505
#define maxm maxn*maxn*4
using namespace std;

int head[maxn*maxn],next[maxm],t[maxm],v[maxm],sum;

int n;

int dis[maxn*maxn],d[maxn*maxn*300];
bool bz[maxn*maxn];

int poi(int x,int y){return (x-1)*n+y;}

void insert(int x,int y,int z){
    t[++sum]=y;
    v[sum]=z;
    next[sum]=head[x];
    head[x]=sum;
}

void spfa(){
    int l=maxn*maxn*100,r=l;
    d[l]=0;
    memset(dis,63,sizeof(dis));
    dis[0]=0;
    bz[0]=1;
    while (l<=r){
        int now=d[l];
        bz[now]=0;
        for(int tmp=head[now];tmp;tmp=next[tmp]){
            if (dis[t[tmp]]>dis[now]+v[tmp]){
                dis[t[tmp]]=dis[now]+v[tmp];
                if (!bz[t[tmp]]){
                    bz[t[tmp]]=1;
                    d[++r]=t[tmp];
                    if (dis[t[tmp]]<dis[d[l+1]]){
                        d[l--]=t[tmp];
                        r--;
                    }
                }
            }
        }
        l++;
    }

}

int main(){
    scanf("%d",&n);
    int x;
    fo(i,1,n) {
        scanf("%d",&x);
        insert(0,poi(1,i),x);
    }
    fo(i,1,n-1)
        fo(j,1,n) {
            scanf("%d",&x);
            insert(poi(i,j),poi(i+1,j),x);
        }
    fo(i,1,n){
        scanf("%d",&x);
        insert(poi(n,i),poi(n+1,1),x);
    }
    fo(i,1,n){
        scanf("%d",&x);
        insert(poi(i,1),poi(n+1,1),x);
        fo(j,2,n){
            scanf("%d",&x);
            insert(poi(i,j),poi(i,j-1),x);
        }
        scanf("%d",&x);
        insert(0,poi(i,n),x);
    }
    fo(i,1,n) scanf("%d",&x);
    fo(i,1,n-1)
        fo(j,1,n){
            scanf("%d",&x);
            insert(poi(i+1,j),poi(i,j),x);
        }
    fo(i,1,n) scanf("%d",&x);
    fo(i,1,n){
        scanf("%d",&x);
        fo(j,1,n-1) {
            scanf("%d",&x);
            insert(poi(i,j),poi(i,j+1),x);
        }
        scanf("%d",&x);
    }
    spfa();
    printf("%d\n",dis[poi(n+1,1)]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值