1. 程式人生 > >P2149 [SDOI2009]Elaxia的路線 - 最短路 - 圖的遍歷

P2149 [SDOI2009]Elaxia的路線 - 最短路 - 圖的遍歷

urn 深搜 答案 ron 情況 同方 clu memset 感覺

註意:矩陣存圖,遍歷邊的時候首先確定邊存在,即g[u][v]要有值,無值說明這條邊不存在,不應該用來更新最短路

他問的是公共路徑最長能有多長,就是盡量讓兩條路的公共路徑長度之和最大,別理解錯題意,比如說題目背景裏面的描述是:一起走的時間盡可能的長。
看起來貌似兩個人以相反的路徑走過相同的一段路不算答案,但是之後替你總結好的題意是這樣的:求無向圖中,兩對點間最短路的最長公共路徑
這裏是無向圖,公共路徑不需要方向相同。因為最短路可能有許多方案,這才導致了公共路徑不是一個定值

我還是對深搜理解不夠啊。。。
考慮建出只有最短路上的邊的圖(有向圖),在這張圖中可以方便地對最短路方案進行標記

只需要在深搜的回溯過程中記錄就好,因為可能從x出發的某最條短路邊並不是最短路方案(s ~ t)上的邊,但是從t回溯回去的邊一定是最短路方案上的邊

有兩種可能出現公共路徑,相遇或者同行,一個最長公共路徑裏面不可能是兩個人既相遇(相遇是指都走一條路但是方向相反)又同行了一段路

考慮兩對點是在同一張圖上面求的最短路,在公共路徑上,要麽他們方向完全相反,要麽就相同,如果說一個人的方向有一段是和另一個人相同,又有一段是相反,這種路徑就應該不是最短路,因為有相同方向又有相反方向,就感覺這個人是刻意繞路

既然他最後反正都要和另一個人方向相同,走的又是同一段路(公共路徑!)他為什麽還要和他反著走?

所以求兩遍,隨便以一個人的起點出發,第一次找同行最長公共路徑,第二次找相遇最長公共路徑

註意寫一些記憶化。。。
這裏是有技巧的。。。圖的遍歷,判重復走過的寫法一般有兩種

void dfs(int x) {
    vis[x] = 1;
    for(int i=head[x]; i; i=e[i].to) {
        int v = e[i].v;
        if(vis[v]) continue;
        calc();
        dfs(v);
    }
}

上面這種寫法,適用於不能重復統計一個點的情況

void dfs(int x) {
    if(vis[x]) return;
    vis[x] = 1;
    for(int i=head[x]; i; i=e[i].to) {
        int v = e[i].v;
        calc();
        dfs(v);
    }
}

這種寫法,適合需要不重復走已經走過的路,而確實需要統計這個點(更多時候是點所連的邊)的情況
你會發現這道題需要用第二種寫法,因為當你搜到一個已經走過的點,這條邊若為公共邊,你仍需要統計這條邊的權值,如果用第一種寫法,搜到這個點,發現曾經走過,直接continue,calc()直接被略過,導致標記最短路,統計權值通通都被跳過了。但是第二種寫法,直到進入了v的遞歸層才會退出,沒進遞歸層之前的操作仍然做了一遍

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 2500 + 10;
typedef long long ll;
int n,m,flg[MAXN],vis[MAXN],g[MAXN][MAXN],dis[MAXN],ans,x1,y1,x2,y2,g1[MAXN][MAXN],g2[MAXN][MAXN];

struct st{
    int id, d;
    bool operator < (const st &a) const{
        return d > a.d;
    }
};
priority_queue<st> q;

void dfs_init(int x, int flg_tag, int t) {
    if(x == t) {
        flg[x] = 1;
        return;
    }
    if(vis[x]) return;
    vis[x] = 1;
    for(int i=1; i<=n; i++) {
        int d = g[x][i];
        if(!d) continue;
        if(dis[x] + d == dis[i]) {
            dfs_init(i, flg_tag, t);
            if(flg[i]) {
                flg[x] = 1;
                if(flg_tag == 1) g1[x][i] = d;
                else g2[x][i] = d;  
            }
        }
    }
}

void work(int s, int flg_tag, int t) {
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, 0, sizeof(vis));    
    dis[s] = 0;
    q.push((st){s, 0});
    while(!q.empty()) {
        st temp = q.top();
        q.pop();
        int x = temp.id, d = temp.d;
        if(vis[x]) continue;
        vis[x] = 1;
        for(int i=1; i<=n; i++) {
            if(g[x][i]) {
                if(dis[i] > d + g[x][i]) {
                    dis[i] = d + g[x][i];
                    q.push((st){i, dis[i]});
                }
            }
        }
    }
    memset(flg, 0, sizeof(flg));
    memset(vis, 0, sizeof(vis));
    dfs_init(s, flg_tag, t);
}

void dfs(int x, int now, int tag) {
    ans = max(ans, now);
    if(vis[x]) return;
    vis[x] = 1;
    for(int i=1; i<=n; i++) {
        int d = g1[x][i];
        if(!d) continue;
        if(tag == 1) {
            if(g2[x][i]) {
                dfs(i, now + g2[x][i], tag);
            } else {
                dfs(i, now, tag);
            }
        } else {
            if(g2[i][x]) {
                dfs(i, now + g2[i][x], tag);
            } else {
                dfs(i, now, tag);
            }
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
    for(int i=1; i<=m; i++) {
        int u,v,l;
        scanf("%d%d%d", &u, &v, &l);
        g[u][v] = g[v][u] = l;
    }
    work(x1, 1, y1), work(x2, 2, y2);
    
    memset(vis, 0, sizeof(vis));
    dfs(x1, 0, 1);
    
    memset(vis, 0, sizeof(vis));
    dfs(x1, 0, 2);
    
    printf("%d\n", ans);
    return 0;
}

P2149 [SDOI2009]Elaxia的路線 - 最短路 - 圖的遍歷