To boldly go where no one has gone before.

【题解】桥 (最短路)

2019-05-16 13:44:59


题面 P2685

分析

题目的意思即:已知一个图,现在要从点$1$走到点$n$,求删掉一条边后最短路的最大值,并求出方案数。

对题目进行分析,可知删去的边一定在原最短路径上。

那么就分别以$1,n$为源点跑一次$\text{Dijsktra}$,得到的距离信息分别是$dis1[u],dis2[u]$。

可以这样想:如果有一条边$<u,v>$不在最短路径上,而经过边$<u,v>$的最短路径与原最短路径的离源点最远的重合点分别为$l[u]$和$r[v]$,可以理解为与源点的$\text{LCA}$。这个可以靠$\text{bfs}$解决。则删掉某条边后,最短路径就有可能变成:$$1\rightarrow l[u]\rightarrow u\rightarrow v\rightarrow r[v]\rightarrow n$$ 由于经过边$<u,v>$的最短路径长度是:$$Dis=dis1[u]+l_{<u,v>}+dis2[v]$$ 那么原最短路径上在$l[u],r[v]-1$两点间所有边的删除都可能使新的路径长度变成$Dis$。由于是最短路,所以显然要取最小值

因此,这个最小值就可以用线段树来维护。 线段树维护的是原最短路径上的边,节点值表示删除区间内的边后最短路的长度。最后可以扫一遍线段树,即可得到答案。

$\text{Dijsktra}$

裸的最短路,复习一下。

void dijsktra(int *dis, int s)
{
    fill(dis, dis + m + 1, INF);
    memset(vis, 0, sizeof(vis));
    dis[s] = 0;
    priority_queue <pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
    q.push(make_pair(0, s));
    while(!q.empty())
    {
        int u = q.top().second;
        q.pop();
        if(vis[u])
            continue;
        vis[u] = true;
        for(int i = last[u]; i; i = edge[i].next)
        {
            int v = edge[i].to;
            if(vis[v])
                continue;
            if(dis[u] + edge[i].val < dis[v])
            {
                dis[v] = dis[u] + edge[i].val;
                q.push(make_pair(dis[v], v));
            }
        }
    }
    return;
}

找到原最短路

暴力即可。

void get_path()
{
    int u = 1;
    while(u != n)
    {
        path[++path_tot] = u;
        id[u] = path_tot;
        for(int i = last[u]; i; i = edge[i].next)
        {
            int v = edge[i].to;
            if(dis2[u] == dis2[v] + edge[i].val)
            {
                in_path[i] = true;
                u = v;
                break;
            }
        }
    }
    path[++path_tot] = n;
    id[n] = path_tot;
    return;
}

$\text{bfs}$

由于是带边权最短路树上$\text{bfs}$,因此可以使用一定的技巧。比如这里,可以加入记忆化搜索的优化方式。

void bfs(int *dis, int *end_point, int s)
{
    queue <int> q;
    q.push(path[s]);
    end_point[path[s]] = s;
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int i = last[u]; i; i = edge[i].next)
        {
            int v = edge[i].to;
            if(!id[v] && !end_point[v] && dis[u] + edge[i].val == dis[v])
            {
                end_point[v] = s;
                q.push(v);
            }
        }
    }
    return;
}

主程序

跑完两遍$\text{Dijsktra}$之后,先找到原路径,然后对于原路径上的每个点进行正反两次$\text{bfs}$,将得到的$\text{LCA}$分别放到$l[u],r[u]$中。此后,枚举每一条不在原路径上的边,对于$l[u]<r[v]$(即有效边)用之前提到的$Dis$计算方法更新线段树,最后一次性$pushdown()$即可得出最后结果。

int main()
{
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= m; i++)
    {
        int s, t, c;
        scanf("%d %d %d", &s, &t, &c);
        build_edge(s, t, c);
        build_edge(t, s, c);
    }
    dijsktra(dis1, 1);
    dijsktra(dis2, n);
    get_path();
    for(int i = 1; i <= path_tot; i++)
        bfs(dis1, l, i);
    for(int i = path_tot; i >= 1; i--)
        bfs(dis2, r, i);
    tr.init(1, 1, path_tot);
    for(int u = 1; u <= n; u++)
    {
        for(int i = last[u]; i; i = edge[i].next)
        {
            int v = edge[i].to;
            if(!in_path[i] && l[u] && r[v] && l[u] < r[v])
                tr.update(1, l[u], r[v] - 1, dis1[u] + edge[i].val + dis2[v]);
        }
    }
    tr.pushdown(1);
    int cnt = 0, ans = 0;
    for(int i = 1; i < path_tot; i++)
    {
        if(res[i] > ans)
        {
            cnt = 1;
            ans = res[i];
        }
        else
        {
            if(res[i] == ans)
                cnt++;
        }
    }
    if(ans == dis1[n])
        cnt = m;
    printf("%d %d", ans, cnt);
    return 0;
}

完整代码

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 500010;
const int INF = 0x3f3f3f3f;
struct EDGE
{
    int to, next, val;
};
struct TREE
{
    int l, r, val;
};
EDGE edge[MAXN << 1];
bool in_path[MAXN], vis[MAXN];
int last[MAXN];
int n, m;
int res[MAXN], dis1[MAXN], dis2[MAXN];
int path[MAXN], id[MAXN], path_tot = 0;
int l[MAXN], r[MAXN];

class SegTree
{
  private:
    TREE tree[MAXN << 3];
  public:
    void init(int x, int l, int r)
    {
        tree[x].l = l, tree[x].r = r;
        tree[x].val = INF;
        if(l == r)
            return;
        int mid = (l + r) >> 1;
        init(x<<1, l, mid);
        init(x<<1|1, mid + 1, r);
        return;
    }
    void update(int x, int l, int r, int key)
    {
        int ll = tree[x].l, rr = tree[x].r;
        if(l <= ll && rr <= r)
        {
            tree[x].val = min(tree[x].val, key);
            return;
        }
        int mid = (ll + rr) >> 1;
        if(l <= mid)
            update(x<<1, l, r, key);
        if(r > mid)
            update(x<<1|1, l, r, key);
        return;
    }
    void pushdown(int x)
    {
        int l = tree[x].l, r = tree[x].r;
        if(l == r)
        {
            res[l] = tree[x].val;
            return;
        }
        tree[x<<1].val = min(tree[x].val, tree[x<<1].val);
        tree[x<<1|1].val = min(tree[x].val, tree[x<<1|1].val);
        pushdown(x<<1);
        pushdown(x<<1|1);
        return;
    }
};
SegTree tr;

void build_edge(int u, int v, int val)
{
    static int id = 0;
    edge[++id].to = v;
    edge[id].val = val;
    edge[id].next = last[u];
    last[u] = id;
    return;
}

void dijsktra(int *dis, int s)
{
    fill(dis, dis + m + 1, INF);
    memset(vis, 0, sizeof(vis));
    dis[s] = 0;
    priority_queue <pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
    q.push(make_pair(0, s));
    while(!q.empty())
    {
        int u = q.top().second;
        q.pop();
        if(vis[u])
            continue;
        vis[u] = true;
        for(int i = last[u]; i; i = edge[i].next)
        {
            int v = edge[i].to;
            if(vis[v])
                continue;
            if(dis[u] + edge[i].val < dis[v])
            {
                dis[v] = dis[u] + edge[i].val;
                q.push(make_pair(dis[v], v));
            }
        }
    }
    return;
}

void get_path()
{
    int u = 1;
    while(u != n)
    {
        path[++path_tot] = u;
        id[u] = path_tot;
        for(int i = last[u]; i; i = edge[i].next)
        {
            int v = edge[i].to;
            if(dis2[u] == dis2[v] + edge[i].val)
            {
                in_path[i] = true;
                u = v;
                break;
            }
        }
    }
    path[++path_tot] = n;
    id[n] = path_tot;
    return;
}

void bfs(int *dis, int *end_point, int s)
{
    queue <int> q;
    q.push(path[s]);
    end_point[path[s]] = s;
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int i = last[u]; i; i = edge[i].next)
        {
            int v = edge[i].to;
            if(!id[v] && !end_point[v] && dis[u] + edge[i].val == dis[v])
            {
                end_point[v] = s;
                q.push(v);
            }
        }
    }
    return;
}

int main()
{
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= m; i++)
    {
        int s, t, c;
        scanf("%d %d %d", &s, &t, &c);
        build_edge(s, t, c);
        build_edge(t, s, c);
    }
    dijsktra(dis1, 1);
    dijsktra(dis2, n);
    get_path();
    for(int i = 1; i <= path_tot; i++)
        bfs(dis1, l, i);
    for(int i = path_tot; i >= 1; i--)
        bfs(dis2, r, i);
    tr.init(1, 1, path_tot);
    for(int u = 1; u <= n; u++)
    {
        for(int i = last[u]; i; i = edge[i].next)
        {
            int v = edge[i].to;
            if(!in_path[i] && l[u] && r[v] && l[u] < r[v])
                tr.update(1, l[u], r[v] - 1, dis1[u] + edge[i].val + dis2[v]);
        }
    }
    tr.pushdown(1);
    int cnt = 0, ans = 0;
    for(int i = 1; i < path_tot; i++)
    {
        if(res[i] > ans)
        {
            cnt = 1;
            ans = res[i];
        }
        else
        {
            if(res[i] == ans)
                cnt++;
        }
    }
    if(ans == dis1[n])
        cnt = m;
    printf("%d %d", ans, cnt);
    return 0;
}