1. 程式人生 > >Luogu P2149 [SDOI2009]Elaxia的路線(最短路+記憶化搜尋)

Luogu P2149 [SDOI2009]Elaxia的路線(最短路+記憶化搜尋)

P2149 [SDOI2009]Elaxia的路線

題意

題目描述

最近,\(Elaxia\)\(w**\)的關係特別好,他們很想整天在一起,但是大學的學習太緊張了,他們必須合理地安排兩個人在一起的時間。

\(Elaxia\)\(w**\)每天都要奔波於宿舍和實驗室之間,他們希望在節約時間的前提下,一起走的時間儘可能的長。

現在已知的是\(Elaxia\)\(w**\)所在的宿舍和實驗室的編號以及學校的地圖:地圖上有\(N\)個路口,\(M\)條路,經過每條路都需要一定的時間。具體地說,就是要求無向圖中,兩對點間最短路的最長公共路徑。

輸入輸出格式

輸入格式:

第一行:兩個整數\(N\)

\(M\)(含義如題目描述)。

第二行:四個整數\(x_1,y_1,x_2,y_2(1\leq x_1 \leq N,1\leq y_1\leq N,1\leq x_2\leq N,1\leq y_2\leq N)\),分別表示\(Elaxia\)的宿舍和實驗室及\(w**\)的宿舍和實驗室的標號(兩對點分別\(x1,y1\)\(x2,y2\))。

接下來\(M\)行:每行三個整數,\(u,v,l(1\leq u\leq N,1\leq v\leq N,1\leq l\leq 10000)\),表示\(u\)\(v\)之間有一條路,經過這條路所需要的時間為\(l\)

輸出格式:

一行,一個整數,表示每天兩人在一起的時間(即最長公共路徑的長度)

輸入輸出樣例

輸入樣例#1:

9 10
1 6 7 8
1 2 1
2 5 2
2 3 3
3 4 2
3 9 5
4 5 3
4 6 4
4 7 2
5 8 1
7 9 1

輸出樣例#1:

3

說明

對於\(30\%\)的資料,\(N\leq 100\)

對於\(60\%\)的資料,\(N\leq 1000\)

對於\(100\%\)的資料,\(N\leq 1500\),輸入資料保證沒有重邊和自環。

思路

等我再調一下,我只有一個小錯誤了... --alecli

首先想想題目要求我們做什麼。我們要求兩組點對之間的最短路,每組點對之間的最短路可能有很多條。然後我們在每組點對中各選出一條最短路,使得它們之間的公共邊的長度之和最長,並輸出這個長度。

那麼我們的演算法流程就是:

  1. 找出第一組點對之間的所有最短路徑。
  2. 找出第二組點對之間的所有最短路徑。
  3. \(dfs\)選兩條路徑,計算公共邊長度。

選出路徑的複雜度已經很高了,再去計算公共邊長度,時間就爆炸了。能不能優化一下呢?我們不妨這樣寫:

  1. 找出第一組點對之間的所有最短路徑。
  2. 把這些最短路徑經過的邊都打上標記。
  3. 找出第二組點對之間的所有最短路徑。
  4. 把這些最短路徑經過的邊都打上標記。
  5. 求被打上兩次標記的邊的邊權之和。

這樣會快很多,不過這又是顯然有問題的,因為會有邊的邊權因為出現在多條最短路徑中而被重複計算,所以我們不得不犧牲一些時間複雜度,這樣改寫:

  1. 找出第一組點對之間的所有最短路徑。
  2. 把這些最短路徑經過的邊都打上標記。
  3. 找出第二組點對之間的所有最短路徑。
  4. \(dfs\)求出一條第二組點對之間的最短路徑,使得這條路徑中已打上標記的路徑的邊權之和最大。

嘿,我們把\(TLE\)的做法和\(WA\)的做法綜合在一起,就得出了\(AC\)的做法!得出結論(霧):

\[TLE+WA=AC\]

另外,還有一些小的優化,比如在操作\(2\)中打標記,我們可以利用\(dfs\),從起點開始找到一條能到達終點的最短路徑,然後把邊全部打上標記。我們可以用記憶化來優化這一部分的時間。設變數\(to_t[i]\)表示是否存在一條經過\(i\)的從\(x_1\)\(y_1\)的最短路。若\(to_t[i]=1\),則有;若\(to_t[i]=-1\),則無;若\(to_t[i]=0\),則表示我們還不知道有沒有,接著搜尋下去才能得到答案:

int dfs1(int u)
{
    if(u==t1) return true;//到達終點
    to_t[u]=-1;
    for(int i=top[u];i;i=nex[i])
        if(dis[to[i]]==dis[u]+len[i])//判斷是否是最短路
        {
            if(to_t[to[i]]==-1) continue;//走不到終點,溜了
            else if(to_t[to[i]]==1||dfs1(to[i])==1)//走得到或者搜尋得到
            {
                to_t[u]=1;
                G[u][to[i]]=G[to[i]][u]=len[i];//G陣列存被標記上的邊權
            }
        }
    return to_t[u];
}

操作\(4\)中第二次\(dfs\)求答案時我們就可以暴力一點,順著跑就好了。當然,優化方式也有很多,在這裡就不舉例了。

//ans[i]記錄從起點到i點所能經過的最大被標記邊權之和
void dfs2(int u)
{
    for(int i=top[u];i;i=nex[i])
        if(dis[to[i]]==dis[u]+len[i]&&ans[to[i]]<G[u][to[i]]+ans[u])//是最短路且對答案有貢獻
        {
            ans[to[i]]=G[u][to[i]]+ans[u];//做記錄
            dfs2(to[i]);//往下搜尋
        }
}

AC程式碼

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1505;
int n,m,s1,t1,s2,t2,dis[MAXN],ans[MAXN],G[MAXN][MAXN],to_t[MAXN];
int cnt,top[MAXN],to[MAXN*MAXN],len[MAXN*MAXN],nex[MAXN*MAXN];
bool vis[MAXN];
int read()
{
    int re=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) re=(re<<3)+(re<<1)+ch-'0',ch=getchar();
    return re;
}
void SPFA(int s)
{
    memset(dis,0x3f,sizeof dis);
    dis[s]=0;
    queue<int>Q;
    Q.push(s);
    while(!Q.empty())
    {
        int now=Q.front();Q.pop();
        vis[now]=false;
        for(int i=top[now];i;i=nex[i])
            if(dis[to[i]]>dis[now]+len[i])
            {
                dis[to[i]]=dis[now]+len[i];
                if(!vis[to[i]])
                {
                    vis[to[i]]=true;
                    Q.push(to[i]);
                }
            }
    }
}
int dfs1(int u)
{
    if(u==t1) return true;
    to_t[u]=-1;
    for(int i=top[u];i;i=nex[i])
        if(dis[to[i]]==dis[u]+len[i])
        {
            if(to_t[to[i]]==-1) continue;
            else if(to_t[to[i]]==1||dfs1(to[i])==1)
            {
                to_t[u]=1;
                G[u][to[i]]=G[to[i]][u]=len[i];
            }
        }
    return to_t[u];
}
void dfs2(int u)
{
    for(int i=top[u];i;i=nex[i])
        if(dis[to[i]]==dis[u]+len[i]&&ans[to[i]]<G[u][to[i]]+ans[u])
        {
            ans[to[i]]=G[u][to[i]]+ans[u];
            dfs2(to[i]);
        }
}
int main()
{
    n=read(),m=read(),s1=read(),t1=read(),s2=read(),t2=read();
    while(m--)
    {
        int x=read(),y=read(),z=read();
        to[++cnt]=y,len[cnt]=z,nex[cnt]=top[x],top[x]=cnt;
        to[++cnt]=x,len[cnt]=z,nex[cnt]=top[y],top[y]=cnt;
    }
    SPFA(s1);
    if(dfs1(s1)==-1)
    {
        printf("0");
        return 0;
    }
    SPFA(s2);
    memset(ans,-1,sizeof ans);
    ans[s2]=0;
    dfs2(s2);
    printf("%d",ans[t2]);
    return 0;
}