1. 程式人生 > >hihocoder1368即 [Offer收割]程式設計練習賽7第四題

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個矩形覆蓋的總面積。