hihocoder1368即 [Offer收割]编程练习赛7第四题

hihocoder1368即
[Offer收割]编程练习赛7第四题:
积水的城市2
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
提示:本题与“积水的城市”相比,数据范围扩大了。

如下图所示,某市市区由M条南北向的大街和N条东西向的道路组成。其中由北向南第i条路和第i+1条路之间的距离是Bi (1 <= i < N),由西向东第i条街和第i+1条街之间的距离是Ai (1 <= i < M)。

https://hihocoder.com/contest/hihointerview19/problem/4

小Ho现在位于第x条路和第y条街的交叉口,他的目的地是第p条路和第q条街的交叉口。由于连日降雨,城市中有K个交叉口积水太深不能通行。小Ho想知道到达目的地的最短路径的长度是多少。

输入
第一行包含两个整数N和M。(1 <= N, M <= 1000)

第二行包含N-1个整数, B1, B2, B3, … BN-1。(1 <= Bi <= 100000)

第三行包含M-1个整数, A1, A2, A3, … AM-1。(1 <= Ai <= 100000)

第四行包含一个整数K,表示积水的交叉口的数目。 (1 <= K <= 30)

以下K行每行包含2个整数,X和Y,表示第X条街和第Y条路的交叉口积水。(1 <= X <= N, 1 <= Y <= M)

第K+5行包含一个整数Q,表示询问的数目。 (1 <= Q <= 1000)

以下Q行每行包含4个整数x, y, p, q,表示小Ho的起止点。起止点保证不在积水的交叉口处。 (1 <= x, p <= N, 1 <= y, q <= M)

输出
对于每组询问,输出最短路的长度。如果小Ho不能到达目的地,输出-1。

样例输入
4 5
2 4 1
3 3 3 2
3
1 3
2 3
3 2
1
1 2 2 4
样例输出
24

稍微有一点意思,是因为,
想了好一会(~20min?忘了),想出正解以后,发现,这idea,对我来说,好像还是高中做的多一点,不下3遍,而大学里我好像真没做过这个idea的了。

说一下思考过程:
直接暴力,O(n^2*q)的时间复杂度,这里这个数据量,受不了。
q次查询逃不开,每次查询专门找路,逃不开。
那就考虑把这个每次查询需要处理的图,给变小。

那怎么变小呢?
考虑这样的网格中,从左上走到右下。
最优解显然是向右走一个一定步数,向下走一定步数。
但是向右和向下的先后顺序,不影响结果,只要向右的步数和向下的步数对了,就行了。

那么,我可以考虑,把中间大段的空白压缩起来,得到一张更小的图。
在这个更小的图上做计算,说不定计算量就刚好够了?

怎么压缩呢?
我们记起点、终点和中间的积水点,为关键点。
然后我们先考虑x轴:取所有关键点的x的值,还有他们右边一个(x+1)的值,以及最左边一个关键点的x的值-1(min(x)-1)(如果有的话),记做x轴上的关键值。
y轴采用类似的处理方式。

举例1:

n=10,m=5 的原图,积水点2个:(3,5),(3,4)
然后求从(2,5)到(4,5) 的最短路。

先对x轴做压缩:
出现的x的值有:2 3 4
对每个值,取往后一个位置,也加入进来:2 3 4 5
对这些值中最小的,取往左一个位置,也加入进来:1 2 3 4 5

这样,我们确定了,新图里只要保留 x = 1 2 3 4 5的情况

然后对y轴做压缩:
出现的y值有:4 5
对每个值,取往后一个位置,也加入进来:4 5
对这些值中最小的,取往左一个位置,也加入进来:3 4 5

这样,我们确定了,新图里只要保留 y = 3 4 5的情况

最终,这个图被压缩到3 * 5的大小,比5 * 10的情况好不少。
考虑可行状态:之前只能先向左,再向下,再向右,这个新图里,并没有丢失这个情况,也没有增加什么新的最短路。
举例2:

n=100,m=100 的原图,积水点2个:(32,55),(40,72)
然后求从(31,55)到(45,72) 的最短路。

先对x轴做压缩:
出现的x的值有:31 32 40 45
对每个值,取往后一个位置,也加入进来:31 32 33 40 41 45 46
对这些值中最小的,取往左一个位置,也加入进来:30 31 32 33 40 41 45 46,
上面这些值,作为新图里的x,分别记做1 2 3 4 … 8,他们之间的距离,用原图第31和30、32和31、33和32、40和33、41和40、45和41、46和45行之间的距离。

然后对y轴做压缩:
出现的y值有:55 72
对每个值,取往后一个位置,也加入进来:55 56 72 73
对这些值中最小的,取往左一个位置,也加入进来:54 55 56 72 73
上面这些值,作为新图里的y,分别记做1 2 3 4 5,他们之间的距离,用原图第54和55、55和56、72和56、73和72列之间的距离。

最终,图被压缩到 8行*5列的大小。
仔细思考可以发现,之前可以通行的第55和第72列之间,仍然有一列空间,可以从55列向右移动,在这中间的一列,调整到需要的行之后,再继续向右移动。所以,可通行性,没有受到影响。
整理出小图里需要的x和y的值之后,把原图里的关键点(积水点、起点、终点)的x和y,对应到这个小图里,直接在上面求起点到终点的最短路即可。

这个小图最大多大?
注意到,只有 K≤30 个积水点,再加上起点和终点,最多只有32个关键点,也就是,最多只会留下65个x值和65个y值。
65 * 65的图,比原来的1000 * 1000的,好太多了!

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
#include <map>
#include <queue>
using namespace std;
typedef long long ll;

const int di[4][2]={{-1,0},{1,0},{0,1},{0,-1}};

struct Point{
    int x,y;
    Point(){}
    Point(int a,int b):x(a),y(b){}
    Point go(int i){
        return Point(x+di[i][0],y+di[i][1]);
    }
    bool check(int n,int m){
        return x>=1&&y>=1&&x<=n&&y<=m;
    }
};

int Alength[1005];
int Blength[1005];

int K;
int cross[35][2];

map<int,int> xaxis,yaxis;
ll dis[105][105];
bool walk[105][105];
int colLength[105];
int rowLength[105];

ll solve(int xa,int ya,int xb,int yb){
    int n=xaxis.size(),m=yaxis.size();
    dis[xa][ya]=0;
    queue<Point> q;
    q.push(Point(xa,ya));
    while(!q.empty()){
        Point x=q.front();q.pop();
        ll disx=dis[x.x][x.y],disy;
        for(int i=0;i<4;++i){
            Point y=x.go(i);
            if(y.check(n,m)&&walk[y.x][y.y]){
                switch(i){
                    case 0:disy=disx+colLength[y.x+1];break;
                    case 1:disy=disx+colLength[y.x];break;
                    case 2:disy=disx+rowLength[y.y];break;
                    case 3:disy=disx+rowLength[y.y+1];break;
                }
                if(disy<dis[y.x][y.y]){
                    dis[y.x][y.y]=disy;
                    q.push(y);
                }
            }
        }
    }
    return dis[xb][yb]==(ll)INT_MAX*100000?-1:dis[xb][yb];
}

void addPoint(int v,map<int,int>& axis){
    axis[v]=1;
    axis[v+1]=1;
}

void fix(map<int,int>& axis,int n,int *orilength,int *length){
    int count=0;
    map<int,int>::iterator it=axis.begin();
    if(it->first!=1) axis[(it->first)-1]=1;
    for(;it=axis.end(),--it,it->first>n;axis.erase(it));
    fill(length,length+105,0);
    for(it=axis.begin();it!=axis.end();++it){
        it->second=++count;
        if(count!=1){
            map<int,int>::iterator pit=it;
            --pit;
            length[count]=orilength[it->first]-orilength[pit->first];
        }
    }
}

inline void out(ll x) {
    if(x<0){
        putchar('-');
        out(-x);
        return;
    }
    if(x>9) out(x/10);
    putchar(x%10+'0');
}

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=2;i<=n;++i) scanf("%d",&Alength[i]),Alength[i]+=Alength[i-1];
    for(int i=2;i<=m;++i) scanf("%d",&Blength[i]),Blength[i]+=Blength[i-1];
    scanf("%d",&K);
    for(int i=0;i<K;++i) scanf("%d%d",&cross[i][0],&cross[i][1]);

    int Q,xa,ya,xb,yb;
    for(scanf("%d",&Q);Q--;){
        scanf("%d%d%d%d",&xa,&ya,&xb,&yb);

        xaxis.clear();yaxis.clear();
        addPoint(xa,xaxis);
        addPoint(xb,xaxis);
        addPoint(ya,yaxis);
        addPoint(yb,yaxis);
        for(int i=0;i<K;++i){
            addPoint(cross[i][0],xaxis);
            addPoint(cross[i][1],yaxis);
        }
        fix(xaxis,n,Alength,colLength);
        fix(yaxis,m,Blength,rowLength);

        fill(dis[0],dis[100]+101,(ll)INT_MAX*100000);
        memset(walk,1,sizeof(walk));
        for(int i=0;i<K;++i){
            walk[xaxis[cross[i][0]]][yaxis[cross[i][1]]]=0;
        }
        out(solve(xaxis[xa],yaxis[ya],xaxis[xb],yaxis[yb]));
        puts("");
    }
    return 0;
}

最后说明一下,这种思路叫做,离散化。借用Matrix67的说法,基本思想就是,只保留我所关心的数值。
比如在这题上,中间没有积水点的一段,怎么走随意,那我只要压缩起来,记录一下从第x列到第y列,同一行下,走过去要多长,就行了。
具体可以参考Matrix67的博客:http://www.matrix67.com/blog/archives/108
(这年头数据挖掘大热,搜离散化,都搜到数据挖掘的时候预处理数据的问题了……)

顺带补充:对这种网格图求最短路的问题,在离散化的时候,需要考虑能绕着不可走的点的边上走过去,所以需要把+1的值也留下,这样就保证,原来能通行的,现在还是可以走了,而且实现最短路的时候还是很方便的。

(真的好久不见这种二维方格图上的离散化了呢~)

拓展练习:POJ 1151
大意:给定 n≤100 个矩形,坐标是在[1,1000000000]之间的正整数.求这n个矩形覆盖的总面积。

重点是压缩思路。
本文地址:http://blog.csdn.net/fcxxzux/article/details/52435342

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值