Luogu P2149 [SDOI2009]Elaxia的路線(最短路+記憶化搜尋)
題意
題目描述
最近,\(Elaxia\)和\(w**\)的關係特別好,他們很想整天在一起,但是大學的學習太緊張了,他們必須合理地安排兩個人在一起的時間。
\(Elaxia\)和\(w**\)每天都要奔波於宿舍和實驗室之間,他們希望在節約時間的前提下,一起走的時間儘可能的長。
現在已知的是\(Elaxia\)和\(w**\)所在的宿舍和實驗室的編號以及學校的地圖:地圖上有\(N\)個路口,\(M\)條路,經過每條路都需要一定的時間。具體地說,就是要求無向圖中,兩對點間最短路的最長公共路徑。
輸入輸出格式
輸入格式:
第一行:兩個整數\(N\)
第二行:四個整數\(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
首先想想題目要求我們做什麼。我們要求兩組點對之間的最短路,每組點對之間的最短路可能有很多條。然後我們在每組點對中各選出一條最短路,使得它們之間的公共邊的長度之和最長,並輸出這個長度。
那麼我們的演算法流程就是:
- 找出第一組點對之間的所有最短路徑。
- 找出第二組點對之間的所有最短路徑。
- \(dfs\)選兩條路徑,計算公共邊長度。
選出路徑的複雜度已經很高了,再去計算公共邊長度,時間就爆炸了。能不能優化一下呢?我們不妨這樣寫:
- 找出第一組點對之間的所有最短路徑。
- 把這些最短路徑經過的邊都打上標記。
- 找出第二組點對之間的所有最短路徑。
- 把這些最短路徑經過的邊都打上標記。
- 求被打上兩次標記的邊的邊權之和。
這樣會快很多,不過這又是顯然有問題的,因為會有邊的邊權因為出現在多條最短路徑中而被重複計算,所以我們不得不犧牲一些時間複雜度,這樣改寫:
- 找出第一組點對之間的所有最短路徑。
- 把這些最短路徑經過的邊都打上標記。
- 找出第二組點對之間的所有最短路徑。
- \(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;
}