NKOJ 3579 (NOI 2010)海拔(平面图最小割+对偶图)

本文针对NOI2010竞赛题目“海拔”进行了解析,介绍了问题背景及目标,并提供了详细的算法思路与实现代码。通过将原问题转化为平面图最小割问题,再进一步转化为对偶图最短路径问题,最终实现了高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

P3579【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想知道在最理想的情况下(即你可以任意假设其他路口的海拔高度),每天上班高峰期间所有人爬坡所消耗的总体力和的最小值。

输入格式

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

输出格式

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

样例输入

1
1
2
3
4
5
6
7
8

样例输出

3

样例说明

样例数据见下图。
这里写图片描述
最理想情况下所有点的海拔如上图所示。
对于20%的数据:n ≤ 3;
对于50%的数据:n ≤ 15;
对于80%的数据:n ≤ 40;
对于100%的数据:1 ≤ n ≤ 500,0 ≤ 流量 ≤ 1,000,000且所有流量均为整数。


拿到这题感觉很吓人,每个点海拔还能是分数,但是有一点比较显然,那就是任意点的海拔都在[0,1][0,1]之间。
进一步的,感觉似乎每个点的海拔都应该是整数,那么不妨假设每个点海拔都是0,10,1之一,不妨来验证一下。
假设有三个点A,B,CA,B,C存在A>B>CA−>B−>C的路径,那么对于A>CA−>C人,体力消耗等于|AC||A−C|,对于A>BA−>B,消耗为sumAB|AB|sumA−B|A−B|,对于B>CB−>C,消耗为sumBC|BC|sumB−C|B−C|,我们发现,最优情况下BB的海拔总是要等于A的海拔或者CC的海拔。由于原图中给定了两个点的海拔,那么图中每个点的海拔必然是011

再进一步观察,最优情况下必然011构成了两个联通块,他们之间有一条分界线,而最终的总体力消耗就是从0走到1的人的体力消耗之和。即分界线上有向流量之和。这个根据上面所说的就比较显然了。

因此一个直接的想法是以左上角为源点,以右下角为汇点跑一个最小割,这个最小割就是答案。但直接这么做复杂度是n6级别的,肯定过不了。

平面图最小割可以转化成对偶图最短路,所以这题直接转化成对偶图求最短路,建对偶图的时候由于是有向边,因此要特别注意连边的方向,这个画个图手算一下就能看出来。

关于平面图最小割转化成对偶图最短路,只需要连接源点和汇点将原来无界的一个面拆成两个,形成一个额外的面,然后原图的面当成点,相邻的面之间连边,边权等于两个面的公共边的容量。然后删掉直接无界面和额外面之间的边。具体可以百度。


代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define N 505
#define M 5000000
using namespace std;
typedef pair<int,int> par;
int n,S,T,WE[N][N],EW[N][N],NS[N][N],SN[N][N];
int TOT,LA[N*N],NE[M],EN[M],LE[M];
int dis[N*N];
priority_queue<par>Q;
void ADD(int x,int y,int z)
{
    TOT++;
    EN[TOT]=y;
    LE[TOT]=z;
    NE[TOT]=LA[x];
    LA[x]=TOT;
}
int Gid(int x,int y)
{
    if(x==0||y==n+1)return S;
    if(x==n+1||y==0)return T;
    return (x-1)*n+y;
}
void Dijkstra(int s)
{
    int i,x,y,d;par t;
    memset(dis,60,sizeof(dis));
    dis[s]=0;Q.push(par(0,s));
    while(Q.size())
    {
        t=Q.top();Q.pop();
        x=t.second;
        d=-t.first;
        if(dis[x]!=d)continue;
        for(i=LA[x];i;i=NE[i])
        {
            y=EN[i];
            if(dis[y]>d+LE[i])
            {
                dis[y]=d+LE[i];
                Q.push(par(-dis[y],y));
            }
        }
    }
}
int main()
{
    int i,j,k,x,y,z;
    scanf("%d",&n);S=n*n+1;T=S+1;
    for(i=1;i<=n+1;i++)
    for(j=1;j<=n;j++)scanf("%d",&x),ADD(Gid(i-1,j),Gid(i,j),x);
    for(i=1;i<=n;i++)
    for(j=1;j<=n+1;j++)scanf("%d",&x),ADD(Gid(i,j),Gid(i,j-1),x);
    for(i=1;i<=n+1;i++)
    for(j=1;j<=n;j++)scanf("%d",&x),ADD(Gid(i,j),Gid(i-1,j),x);
    for(i=1;i<=n;i++)
    for(j=1;j<=n+1;j++)scanf("%d",&x),ADD(Gid(i,j-1),Gid(i,j),x);
    Dijkstra(S);
    printf("%lld",dis[T]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值