1. 程式人生 > >旅行計劃(題解)

旅行計劃(題解)

最終 while 沒有 復雜度 ron 答案 發現 pair 表示

【問題描述】
就要放暑假了。忙碌了一個學期的Sharon 準備前往W 國進行為期長達一個
月的旅行。然而旅行往往是疲憊的,因此她需要來規劃一下自己的旅行路線。
W 國由n 個城市組成,我們不妨將每個城市看成是一個二維平面上的點。
對於第i 個城市,我們設它所處的位置為( , ) i i x y 。W 國的人做事一向循規蹈矩,
因此在從城市i 向城市j 移動的過程中,只能沿水平或豎直方向移動。換句話說,
城市i 和城市 j 的最短距離為 i, j i j i j d ? x ? x ? y ? y 。
當Sharon 為自己設計一條旅行路線時,她會選好起點城市S 和終點城市T。
然後考慮從城市S 通過一些中轉城市最終到達城市T。
我們不妨設城市S 到城市T 的中轉城市依次為1 2 , ,..., k a a a 。則Sharon 首先從
城市S 出發到達城市1a ,對於任意的1 ik?? ,她將從城市ia 出發到達城市1 i a ? 。
最後從城市k a 出發到達城市T。顯然對於中途的k 個中轉城市,她將行走1 k ? 段
路。我們定義
1 S,a1 D ? d ,對於任意的2 ? i ? k,
1 , i i i a a D d
?
? , k 1 ak ,T D d ? ? 。
既然已經放假了,Sharon 當然不希望這次旅行過於疲憊,因此我們定義一條
旅行路線的疲勞值為? ?
1 1
max i
i k
R D
? ? ?
? 。現在Sharon 找到善於編程的你,她將告訴
你W 國城市的個數,每個城市的坐標,以及她選定的起點城市S 和終點城市T。
她想由你來幫她安排旅行的中轉城市,使得R 的值最小。
特別註意的是,我們允許從城市S 不經過任何中轉城市到達城市T,也就是
說可以從城市S 直接前往城市T,此時的疲勞值為T S T S R x x y y ? ? ? ? 。當ST?
時,我們規定0 R ? 。
【輸入格式】
第一行包含一個正整數n 表示W 國城市的個數。
接下來n 行每行包含兩個正整數, i x 和i y ,表示第i 個城市的坐標。
接下來一行包含一個正整數m,表示Sharon 旅行的次數。
接下來m 行每行包含兩個正整數,S 和T,表示起點城市和終點城市。
【輸出格式】
輸出共包含m 行,其中第i 行包含一個正整數Ri,表示第i 次旅行的最小疲
勞值。
NOI 2016 模擬訓練 旅行計劃
第6 頁 共8 頁
【樣例輸入】
3
1 1
2 1
4 1
2
1 2
1 3
【樣例輸出】
1
2
【樣例說明】
第一次旅行時直接從城市1 前往城市2,疲勞值為1。
第二次旅行時從城市1 經過中轉城市2 再到達城市3,疲勞值為2。

剛拿到這道題,以為很簡單,但只是想到了暴力的算法,就是把每個點之間的距離算出來再進行加和比較。

但是到後面卻發現這道題沒有想象中的那麽簡單,你不只要算出每個的距離,你還得比較加上他之後的距離的最大值來判斷取舍,顯然這種枚舉的方法復雜度是很大的。

後來我又想到的是DFS,思路是:從你出發的點開始尋找,只能走上下左右四個方向,且每次都是一步,這樣就保證了與單位長度是一致的。然後只要找到一個點,就比較這個點到起點的距離與這個點到終點的距離的最大值與你記錄的上一個點的數據的最小值來決定這個點到底走不走(如果要走的點是除了起點外的第一個點的話,就可以直接走上去),所以一直搜索下去,就可以保證到達終點的距離是最短的了。

這裏有個題解,是每種方法的得分情況:

算法一 :

使用類似 Floyd的算法預來處理任意兩點間答案,記 ans[i][j]表 示從點 i走到點 走到點 j最小的大距離值。每次用 最小的大距離值。每次用 最小的大距離值。每次用 max(ans[i][k],ans[k][j])來更新 來更新 ans[i][j]。 預處理復雜度 O(n3),單次詢問復雜度 O(1)。期望得分 。期望得分 20分。

算法二 :

註意到測試點 :註意到測試點 3只有一次詢問, 測試點 1和 2的點數及詢問都很 小。 因此可以二分答案後從 S使用 BFS來擴展看能否走到 來擴展看能否走到 T,然後調整二分邊 ,然後調整二分邊 界。期望得分 30分。

算法三 :

易知我們的行走過程一定要在原圖最小生成樹上,而 :易知我們的行走過程一定要在原圖最小生成樹上,而 :易知我們的行走過程一定要在原圖最小生成樹上,而 n個點 的圖共有約 n2/2條無向邊,求出最小生成 樹問題轉換條無向邊,求出最小生成 樹問題轉換m次詢問樹上兩點間 最大邊權。預處理復雜度 O(n2logn)或 O(n2),單次詢問復雜度 ,單次詢問復雜度 O(n)。期望得分 40分。

算法四 :

註意到測試點 5和 6所有的點都滿足 xi = 1,因此所有的點將構成 ,因此所有的點將構成 一條鏈。每次的行走過程定是從個點到它上面或者下,將所 一條鏈。每次的行走過程定是從個點到它上面或者下,將所 一條鏈。每次的行走過程定是從個點到它上面或者下,將所 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 有的點按照縱坐標排序,這樣每次詢問就變成求給定區間 [L,R]的最小值,使用 的最小值,使用 的最小值,使用 的最小值,使用 的最小值,使用 的最小值,使用 的最小值,使用 ST表或線段樹等數據結構進行處理。期望得分 20分。結合算法三 。期望得分 60分。

算法五 :

我們發現算法的瓶頸主要出在預處理最小生成樹上。對於平面點 :我們發現算法的瓶頸主要出在預處理最小生成樹上。對於平面點 :我們發現算法的瓶頸主要出在預處理最小生成樹上。對於平面點 曼哈頓距離最小生成樹有一些性質可以利用。對於個點 i,我們以它為原點建 立平面直角坐標系,並畫出線 y = x和 y = ?x。這 樣整個平面就被坐標軸和兩條直線分成了 8個部分,可以證明的是點 個部分,可以證明的是點 i只需要向每個部分離它最近的點連 邊然後求最小生成樹即可。這樣數就變了 8n條,可以快速求出最小生成樹 條,可以快速求出最小生成樹 了。接下來我們考慮如何構造這 8n條邊,如果對每個點都去尋找這 條邊,如果對每個點都去尋找這 8個部分離 它最近的點,建邊復雜度將再次退化為 O(n2)。一個可行的做法是將所有 的點按 照橫坐標為第一關鍵字縱二排序,然後使用線段樹或 狀數組照橫坐標為第一關鍵字縱二排序,然後使用線段樹或 狀數組平衡樹等數據結構維護點之間的關系即可做到 O(nlogn)的預處理復雜度。接下來 的預處理復雜度。接下來 的預處理復雜度。接下來 我們考慮每次詢問,設樹的高度為 我們考慮每次詢問,設樹的高度為 h,則單次詢問的復雜度就為 ,則單次詢問的復雜度就為 O(h)。對於前 。對於前 4個測試點 n都較小,對於第 都較小,對於第 5和第 6個測試點可以通過算法四解決,對於第 個測試點可以通過算法四解決,對於第 7和第 8個測試點由於數據隨機生成,這樣樹的高度不會特別。因此結合算法四 個測試點由於數據隨機生成,這樣樹的高度不會特別。因此結合算法四 個測試點由於數據隨機生成,這樣樹的高度不會特別。因此結合算法四 以後期望得分 80分。

算法六 :

使用算法五預處理出最小生成樹,我們考慮優化的詢問部分。 :使用算法五預處理出最小生成樹,我們考慮優化的詢問部分。 :使用算法五預處理出最小生成樹,我們考慮優化的詢問部分。 每次詢問樹上兩點間的最大邊權,使用鏈剖分預處理復雜度 每次詢問樹上兩點間的最大邊權,使用鏈剖分預處理復雜度 每次詢問樹上兩點間的最大邊權,使用鏈剖分預處理復雜度 O(nlogn),單次 ,單次 詢問復雜度 O(log2n)。期望得分 。期望得分 80~90分。使用動態樹,預處理復雜度 分。使用動態樹,預處理復雜度 分。使用動態樹,預處理復雜度 分。使用動態樹,預處理復雜度 O(nlogn), 單次詢問復雜度 O(logn)。期望得分 80~90分。以上兩種算法雖然可高效解決 分。以上兩種算法雖然可高效解決 樹上的很多詢問題,但由於常數過大等原因無法通所有測試據。我們考慮 樹上的很多詢問題,但由於常數過大等原因無法通所有測試據。我們考慮 樹上的很多詢問題,但由於常數過大等原因無法通所有測試據。我們考慮 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 到樹的形態固定,邊權因此我們可以使用倍增方式預處理點 i往上走 往上走 2j條邊 的最大邊權,這樣預處理復雜度 O(nlogn),單次詢問復雜度 O(logn),常數 很小。期望得分 100分

代碼:

#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <vector>
#include <algorithm>
#define mp make_pair
#define fi first
#define se second

using namespace std;

typedef pair<int,int> PII;
const int MAXN=100005;
const int INF=1000000000;

struct Point{
    int x,y,num;
    friend bool operator < (const Point &a,const Point &b)
    {
        if (a.x!=b.x) return a.x<b.x;
        return a.y<b.y;
    }
}pnt[MAXN];

struct E{
    int a,b,w;
    E(void){}
    E(int _a,int _b,int _w):a(_a),b(_b),w(_w){}
    friend bool operator < (const E &a,const E &b)
    {
        return a.w<b.w;
    }
}e[MAXN*4];
int en;

struct G{
    int to,next,d;
}g[MAXN*2];
int gn,start[MAXN];

inline void Add(int a,int b,int d)
{
    gn++,g[gn].to=b,g[gn].d=d,g[gn].next=start[a],start[a]=gn;
}

int n,m;
int fa[MAXN];
int ffa[20][MAXN];
int dis[20][MAXN];
int a[MAXN],b[MAXN];
PII c[MAXN];
int Log[MAXN];
int dep[MAXN];

inline void Update(int x,PII v)
{
    for (int i=x;i<=n;i+=i&(-i)) c[i]=min(c[i],v);
}

inline PII Query(int x)
{
    PII res=mp(INF,-1);
    for (int i=x;i;i-=i&(-i)) res=min(res,c[i]);
    return res;
}

int getfa(int x)
{
    if (fa[x]!=x) fa[x]=getfa(fa[x]);
    return fa[x];
}

void merge(int x,int y)
{
    fa[getfa(x)]=getfa(y);
}

inline int LCA(int p,int q)
{
    if (dep[p]<dep[q]) swap(p,q);
    int x=dep[p]-dep[q];
    for (int i=0;i<=Log[x];i++)
        if (x&(1<<i)) p=ffa[i][p];
    for (int i=Log[n];i>=0;i--)
        if (ffa[i][p]!=ffa[i][q]) p=ffa[i][p],q=ffa[i][q];
    if (p==q) return p;
    return ffa[0][p];
}

inline int Query(int p,int q)
{
    int x=dep[p]-dep[q];
    int res=0;
    for (int i=0;i<=Log[x];i++)
        if (x&(1<<i)) res=max(res,dis[i][p]),p=ffa[i][p];
    return res;
}

void Init()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%d%d",&pnt[i].x,&pnt[i].y);
        pnt[i].num=i;
    }
}

void BuildMST()
{
    for (int dir=1;dir<=4;dir++)
    {
        if (dir==2||dir==4)
            for (int i=1;i<=n;i++) swap(pnt[i].x,pnt[i].y);
        if (dir==3)
            for (int i=1;i<=n;i++) pnt[i].x=-pnt[i].x;
        sort(pnt+1,pnt+n+1);
        for (int i=1;i<=n;i++) a[i]=b[i]=pnt[i].x-pnt[i].y;
        sort(b+1,b+n+1);
        int nq=unique(b+1,b+n+1)-(b+1);
        for (int i=1;i<=n;i++)
        {
            a[i]=lower_bound(b+1,b+nq+1,a[i])-b;
            c[i]=mp(INF,-1);
        }
        for (int i=n;i;i--)
        {
            int now=Query(a[i]).se;
            if (now!=-1) e[++en]=E(pnt[i].num,pnt[now].num,pnt[now].x+pnt[now].y-pnt[i].x-pnt[i].y);
            Update(a[i],mp(pnt[i].x+pnt[i].y,i));
        }
    }
    for (int i=1;i<=n;i++) fa[i]=i;
    sort(e+1,e+en+1);
    for (int i=1;i<=en;i++)
    {
        if (getfa(e[i].a)==getfa(e[i].b)) continue;
        merge(e[i].a,e[i].b);
        Add(e[i].a,e[i].b,e[i].w);
        Add(e[i].b,e[i].a,e[i].w);
    }
}

void BuildTree()
{
    static int qu[MAXN];
    static bool vis[MAXN];
    int head=0,tail=1;
    qu[0]=1;
    vis[1]=true;
    while (head!=tail)
    {
        int p=qu[head++];
        for (int i=start[p];i;i=g[i].next)
        {
            int v=g[i].to,d=g[i].d;
            if (vis[v]) continue;
            vis[v]=true;
            dep[v]=dep[p]+1;
            dis[0][v]=d;
            ffa[0][v]=p;
            qu[tail++]=v;
        }
    }
    Log[0]=-1;
    for (int i=1;i<=n;i++) Log[i]=Log[i>>1]+1;
    for (int i=1;i<=Log[n];i++)
        for (int j=1;j<=n;j++)
        {
            ffa[i][j]=ffa[i-1][ffa[i-1][j]];
            dis[i][j]=max(dis[i-1][j],dis[i-1][ffa[i-1][j]]);
        }
}

void Solve()
{
    scanf("%d",&m);
    while (m--)
    {
        int p,q,lca;
        scanf("%d%d",&p,&q);
        lca=LCA(p,q);
        printf("%d\n",max(Query(p,lca),Query(q,lca)));
    }
}

int main()
{
    freopen ("plan.in","r",stdin);
    freopen ("plan.out","w",stdout);
    Init();
    BuildMST();
    BuildTree();
    Solve();
    return 0;
}

旅行計劃(題解)