本文地址: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,对应到这个小图里,直接在上面求起点到终点的最短路即可。
这个小图最大多大?
注意到,只有 K≤30 个积水点,再加上起点和终点,最多只有32个关键点,也就是,最多只会留下65个x值和65个y值。
65 * 65的图,比原来的1000 * 1000的,好太多了!
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 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(disyreturn 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;iscanf("%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;i0],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;i0]]][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 个矩形,坐标是在[0,100000]之间的正实数.求这n个矩形覆盖的总面积。