【SDOI2009】Elaxia的路线(最短路、最短路径图,拓扑排序)


题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1880

题目链接:https://www.luogu.com.cn/problem/P2149

题意

求无向图中,两对点间最短路的最长公共路径。

做法

这应该不是标算(乱搞踩标算

  • 假如两对点分别是$(a,b)$和$(c,d)$,要求$a$到$b$的最短路和$c$到$d$的最短路的最长公共路径。
  • 首先SPFA(其实有时候挺好用的)求出$a$到$b$的最短路,然后标记出该图中所有满足$dis(u) + w(u,v) = dis(v)$并且在$a$到$b$的最短路径上的有向边$(u,v)$。
  • 构成一个从$a$到$b$的最短路径图,是一个DAG
  • 然后同理SPFA求$c$到$d$的最短路,同理找出该图中所有满足$dis(u) + w(u,v) = dis(v)$并且在$c$到$d$的最短路径上的有向边$(u,v)$。这一次连边重新建图:如果这条有向边$(u,v)$被标记过,边权设为该边原本边权。否则边权为0,并记录下来这条边原本的边权(后面有用)。在这个新建的有向无环图上用拓扑排序DP统计答案。
  • 然后把上面重新建的图反过来(这是我这个题解的特色)。原来边权为0的改为原本的边权(上面记录的这个时候用到),不为0的改为0,在这个图上再一次用拓扑排序DP统计答案。
  • 答案为两次拓扑排序的最大值。

比较有价值的部分是我的建图,可能这个题不是正解,但是我的两点间最短路径图的求法是我写着博客的主要原因,具体看代码。

代码

#include 
#include 
#include 
#include 
#define Re register
using namespace std;
inline int read() {
    Re int x = 0;
    char ch = getchar();
    while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return x;
}
const int maxN = 1505, maxM = 300005;
int N, M, A, B, C, D, f[maxN];
struct Edge1 {
    int nxt, to, dis;
} e[maxM << 1];
int head[maxN], cnte = 1;
struct Edge2 {
    int nxt, to, dis, from;
} ee[maxM << 1];
int hhead[maxN], ccnte = 1;
inline void add_Edge(int i, int j, int k) {
    e[++cnte].nxt = head[i];
    e[cnte].dis = k;
    e[cnte].to = j;
    head[i] = cnte;
}
inline void add_edge(int i, int j, int k) {
    ee[++ccnte].dis = k;
    ee[ccnte].nxt = hhead[i];
    ee[ccnte].to = j;
    ee[ccnte].from = i;
    hhead[i] = ccnte;
}
int dis[maxN];
bool vis[maxN], is[maxN], have[maxN][maxN];
void SPFA(int S) {
    queue<int> Q;
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    vis[S] = true, Q.push(S), dis[S] = 0;
    while (!Q.empty()) {
        Re int u = Q.front();
        Q.pop();
        vis[u] = false;
        for (Re int v, i = head[u]; i; i = e[i].nxt) {
            if (dis[v = e[i].to] > dis[u] + e[i].dis) {
                dis[v] = dis[u] + e[i].dis;
                if (!vis[v]) 
                    Q.push(v), vis[v] = true;
            }
        }
    }
}
//第一次求两点之间的最短路径图 
int dfs1(int u, int S, int T, int len) { 
    vis[u] = true;
    if (u == T && len == dis[T]) //如果能到达终点并且是最短路就返回1 
        return is[u] = true;
    Re bool flag = false;
    for (Re int i = head[u]; i; i = e[i].nxt) {
        Re int v = e[i].to;
        if (dis[u] + e[i].dis == dis[v]) {
/*------保证每一个点最多访问一次,复杂度在这里降为O(n)------*/    
            if (vis[v] && is[v]) {
                flag = have[u][v] = true;                
            }
            else if (vis[v]) continue;
/*---------------------接应上面分割线-----------------------*/
            else if (dfs1(v, S, T, len + e[i].dis)) 
                flag = have[u][v] = true;
        }
    }
    return is[u] = flag;
}
int ind[maxN], W[maxM];
int dfs2(int u, int S, int T, int len) {
    vis[u] = true;
    if (u == T && len == dis[T]) 
        return is[u] = true;
    Re bool flag = false;
    for (Re int i = head[u]; i; i = e[i].nxt) {
        Re int v = e[i].to;
        if (dis[u] + e[i].dis == dis[v]) {
            if (vis[v] && is[v]) {
                flag = true;
                if (have[u][v]) 
                    add_edge(u, v, e[i].dis), ++ind[v];            
                else {
                    add_edge(u, v, 0), ++ind[v];
                    if (have[v][u])
                        W[ccnte] = e[i].dis;    //记录原本的边            
                }
            }
            else if (vis[v]) continue;
            else if (dfs2(v, S, T, len + e[i].dis)) {
                flag = true;
                if (have[u][v]) 
                    add_edge(u, v, e[i].dis), ++ind[v];            
                else {
                    add_edge(u, v, 0), ++ind[v];
                    if (have[v][u]) 
                        W[ccnte] = e[i].dis;    //记录原本的边                    
                }
        
            }
        }
    }
    return is[u] = flag;
}
void DP() {
    memset(f, 0, sizeof(f));
    queue<int> Q;
    for (int i = 1; i <= N; ++i) {
        if (!ind[i]) Q.push(i);    
    } 
    while (!Q.empty()) {
        Re int u = Q.front();
        Q.pop();
        for (int i = hhead[u]; i; i = ee[i].nxt) {
            Re int v = ee[i].to;
            f[v] = max(f[v], f[u] + ee[i].dis);
            --ind[v];
            if (!ind[v]) Q.push(v);
        }
    }
}
int main() {
    N = read(), M = read();
    A = read(), B = read(), C = read(), D = read();
    for (Re int i = 1; i <= M; ++i) {
        Re int u, v, w;
        u = read(), v = read(), w = read();
        add_Edge(u, v, w), add_Edge(v, u, w);
    }
    SPFA(A);
    memset(vis, 0, sizeof(vis));
    dfs1(A, A, B, 0);
    SPFA(C);
    memset(vis, 0, sizeof(vis));
    memset(is, 0, sizeof(is));
    dfs2(C, C, D, 0);
    DP();
    memset(ind, 0, sizeof(ind));
    int tmp = f[D];
    /*------------------------------*/
    //蒟蒻我今天才知道原来清除一个图只需要把head数组和cnte初始化就好了
    //正是利用了这个,才能如此方便的把图反过来,这得感谢Universal OJ群里的热心群友回答我的问题 
    memset(hhead, 0, sizeof(hhead));
    int k = ccnte;
    ccnte = 1;
    /*------------------------------*/
    for (Re int i = 2; i <= k; ++i) {
        Re int u = ee[i].from, v = ee[i].to, w = W[i];
        add_edge(v, u, w);
        ++ind[u];
    }
    DP();
    tmp = max(tmp, f[C]);
    printf("%d\n", tmp);
    return 0;//完结撒花~ 
}