【题解】NOI-2010-海拔

这是一篇关于NOI-2010比赛题目的解析,主要讨论如何解决在网格图中,考虑到海拔高度变化,使得所有人爬坡消耗的总体力和最小的问题。文章提出了从20分到100分的不同解法,包括通过压缩点权、枚举分界线、最小割和最短路等算法策略,适合对算法和网络流感兴趣的读者。

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

题目描述

YT市是一个规划良好的城市,城市被东西向和南北向的主干道划分为n×nn×n个区域。简单起见,可以将YT市看作 一个正方形,每一个区域也可看作一个正方形。从而,YT城市中包括(n+1)×(n+1)(n+1)×(n+1)个交叉路口和2n×(n+1)2n×(n+1)条双向道路(简称道路),每条双向 道路连接主干道上两个相邻的交叉路口。下图为一张YT市的地图(n=2n=2),城市被划分为2×22×2个区域,包括3×33×3个交叉路口和1212条双向道路。

看啥嘞

小Z作为该市的市长,他根据统计信息得到了每天上班高峰期间YT市每条道路两个方向的人流量,即在高峰期间沿 着该方向通过这条道路的人数。每一个交叉路口都有不同的海拔高度值,YT市市民认为爬坡是一件非常累的事情,每向上爬hh的高度,就需要消耗h的体力。如果 是下坡的话,则不需要耗费体力。因此如果一段道路的终点海拔减去起点海拔的值为hh(注意h可能是负数),那么一个人经过这段路所消耗的体力是max(0,h)max(0,h)

小Z还测量得到这个城市西北角的交叉路口海拔为00,东南角的交叉路口海拔为1(如上图所示),但其它交叉路口的海拔高度都无法得知。小Z想知道在最理想的情况下(即你可以任意假设其他路口的海拔高度),每天上班高峰期间所有人爬坡消耗的总体力和的最小值。

输入输出格式

输入格式:

第一行包含一个整数n,含义如上文所示。

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

输出格式:

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

输入输出样例

输入样例#1:

1
1
2
3
4
5
6
7
8

输出样例#1:

3

说明

看啥嘞

对于20%的数据:n3n≤3

对于50%的数据:n15n≤15

对于80%的数据:n40n≤40

对于100%的数据:1n5e21≤n≤5e201e60≤流量≤1e6且所有流量均为整数。

题目概要

给定一方格图,每条边双向权值不同,每条边的贡献定义为 ×max(vu,0)边的权值×max(v−u,0),而每个点的点权均不知,仅知左上角点权为00,右下角点权为1,求所有边贡献和最小值

思路

首先这题一看就不是一道友善的题,谁知道每个点点权啊,难道要每个枚举,然而每个点的点权又没有限制,小到无穷小,大到long long都hold不住,暴力分在(=@__@=)哪里?

算了,这题还是从拿部分分开始考虑吧

首先我们看到2020分数据n3n≤3 我们就知道复杂度肯定贼大,如果断然枚举,每一个点上点权甚至无数据范围,连时间复杂度都算不出来。

但是我们可以发现,如果各点点权大于11,我们把它们降为1,则贡献和一定更小(因为他们之间的贡献将为零,而他们与其他点之间的每一条贡献都会减小(因为海拔差一定减小)),相应地,我们发现如若点权的上界为11,那么可以同样推出点权下界为0,那么我们就把点权压缩在001之间了。

但是001之间的数有无穷多个,枚举的话用天河跑到我不爆零的那天也跑不完,所以我们尽量让点权确定在一个常数区间,我们大胆地假想一下如果所有点权均为0101,那么贡献总值一定最小(证明:如果在点权全为0101的情况下将其更改为0 10 1之间的实数,则与该点相关的贡献一定增大(因为原图是稳定在贡献和最小的情况下的,且每个点增加或减少所带来的贡献度变化值正负情况一定是不变的,而为了保证贡献值最小,该点权一定会取到极限情况,也就是001))

这时发现每个点只可能是0101,暴力枚举复杂度在O(2n2)O(2n2),终于可以拿到暴力分了/(ㄒoㄒ)/~~

虽然为了拿这2020分讲了这么多,但我看网上大多数都把这步跳过了,所以我就写了一份稍微详细一点的解析送给像我一样的蒟蒻

好了,让我们迈向更高分的暴力,我们又发现,由于整个矩阵就是0101矩阵了,用一个小小的贪心就可以发现权值为00的点与权值为1的点是分别联通的(我们再证明一下:若当前有一个0被1围绕着,那么我们将0变为1可以减小海拔差为0,那么可以进一步地降低贡献,证明0是联通的,反之证明1也是联通的)我们便可以发现0和1之间有一条分界线

所以我们只要枚举分界线即可解决5050(本蒟蒻因为不会打插头所以未测,所以5050仅是预估)

这时就有dalao跳出来了:

这不就是一道最小割的裸题吗?

(o)o[BINGO!](o゜▽゜)o☆[BINGO!]

dalao就是dalao,一下就擦到了正解,为什么是“擦”到了正解呢?

原因是最大流强大的复杂度为O(V2E)O(V2E) 而本题中EVE≈V,导致时间复杂度为三次方级别,只有8080这是本蒟蒻第一次发现网络流这种高级算法作为暴力分出现

这时就引入一种蒟蒻忒喜欢的算法——最短路

至于最短路和网络流有啥关系,就想想,如果要将左上角到右下角的关系全数切断,就可以视为有一条从左下角到右上角的贯穿矩阵的线,而这条线一定是由多条跨越流路的线链接而成(其实就是多条割线),如果将方格作为点,跨越流路的的割作为路径,就可以将最小割问题转化为从左下角外围方格到右上角外围方格的最短路问题,用上堆优化的dijkstra(本蒟蒻懒,用SLF的SPFA),复杂度O(n2logn2)=O(n2logn)O(n2logn2)=O(n2logn) 其实就是一个网络流转化为对偶图求最短路的模板题

代码

#include<bits/stdc++.h>
using namespace std;
#define rg register
#define cl1(x) memset(x,-1,sizeof(x))

template <typename _Tp> inline void read(_Tp &x){
    char c11=getchar();x=0;while(c11<'0'||c11>'9')c11=getchar();
    while(c11>='0'&&c11<='9'){x=x*10+c11-'0';c11=getchar();}
}

const int maxn=505,maxx=1e6+1e4;
int n,s=maxx-2,t=maxx-1;
int dis[maxx];
struct node{int v,nxt,w;}a[maxx];
int head[maxx],p=0;

inline void add(int,int,int);

int spfa(){
    deque <int> q;
    cl1(dis);
    dis[s]=0;
    q.push_back(s);
    while(!q.empty()){
        int x=q.front();
        q.pop_front();
        for(rg int i=head[x];i;i=a[i].nxt)
            if(dis[a[i].v]==-1||dis[a[i].v]>dis[x]+a[i].w){
                dis[a[i].v]=dis[x]+a[i].w;
                if(dis[a[i].v]<dis[q.front()])q.push_front(a[i].v);
                else q.push_back(a[i].v);
            }
    }
    return dis[t];
}

/*int spfa(){
    queue <int> q;
    cl1(dis);
    dis[s]=0;
    q.push(s);
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(rg int i=head[x];i;i=a[i].nxt)
            if(dis[a[i].v]==-1||dis[a[i].v]>dis[x]+a[i].w){
                dis[a[i].v]=dis[x]+a[i].w;
                if(dis[a[i].v]<dis[q.front()])q.push(a[i].v);
                else q.push(a[i].v);
            }
    }
    return dis[t];
}*/

void init(){
    read(n);                    // left-->right   up-->down
    int x;
    for(rg int i=0;i<=n;i++)
    for(rg int j=1;j<=n;j++){
        read(x);
        if(i==n)    add(s,(i-1)*n+j,x);
        else if(!i) add(j,t,x);
        else add(i*n+j,(i-1)*n+j,x);
    }
    for(rg int i=1;i<=n;i++)
    for(rg int j=0;j<=n;j++){
        read(x);
        if(!j) add(s,(i-1)*n+j+1,x);
        else if(j==n) add(i*n,t,x);
        else add((i-1)*n+j,(i-1)*n+j+1,x);
    }
    for(rg int i=0;i<=n;i++)
    for(rg int j=1;j<=n;j++){
        read(x);
        if(i==n) add((i-1)*n+j,s,x);
        else if(!i) add(t,j,x);
        else add((i-1)*n+j,i*n+j,x);
    }
    for(rg int i=1;i<=n;i++)
    for(rg int j=0;j<=n;j++){
        read(x);
        if(!j) add((i-1)*n+j+1,s,x);
        else if(j==n) add(t,i*n,x);
        else add((i-1)*n+j+1,(i-1)*n+j,x);
    }
}

int main(){
    init();
    printf("%d\n",spfa());
    return 0;
}

inline void add(int u,int v,int w){
    a[++p].v=v;
    a[p].w=w;
    a[p].nxt=head[u];
    head[u]=p;
}

好像发现这题的做法还是挺丰富的吼

2020–>数论+贪心(乱搞)

5050–>插头Dp

8080–>最小割

100100–>对偶图求最短路

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值