【在线笔试题解题报告系列】hihocoder #1368 积水的城市2

来源:互联网 发布:考勤系统设计sql 编辑:程序博客网 时间:2024/04/28 11:00

本文地址:http://blog.csdn.net/fcxxzux/article/details/52435342

@帝王铠 向我问了一个稍微有一点意思的题:
hihocoder #1368 积水的城市2

#1368 : 积水的城市2时间限制:10000ms单点时限:1000ms内存限制:256MB描述提示:本题与“积水的城市”(1354)相比,数据范围扩大了。如下图所示,某市市区由M条南北向的大街和N条东西向的道路组成。其中由北向南第i条路和第i+1条路之间的距离是Bi (1 <= i < N),由西向东第i条街和第i+1条街之间的距离是Ai (1 <= i < M)。

小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,对应到这个小图里,直接在上面求起点到终点的最短路即可。

这个小图最大多大?
注意到,只有 K30 个积水点,再加上起点和终点,最多只有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
大意:给定 n100 个矩形,坐标是在[0,100000]之间的正实数.求这n个矩形覆盖的总面积。

0 0