洛谷 P2934 安全路径

倖福魔咒の 提交于 2020-04-08 00:48:21

咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕


洛谷 P2934 安全路径

KONO题面哒……然而并没有完整题面

题目大意:

给出一个N节点、M条边的无向图,求出到每个节点的安全路径的最小长度。

安全路径定义:不经过1号点到当前节点的最短路的最后一条边的路径。

数据范围:

\(N\leq10^5,M\leq2*10^5\)


提供可并堆(中的左偏树)因为只学了左偏树的做法。

吐槽,题面中样例说明的迫真连通图稍微有那么一点点难看。

当然,要是解析出了题目的要点,这道题和样例说明还是挺好理解的。但做法稍微有点绕。

首先我们想到把整个连通图浓缩成一个最短路径树(即每条边都位于原图最短路上的树)。

然后原连通图就会变成介个样子。

那么,我们把原图缩成最短路径树有什么用呢?由于安全路径的定义,所以在寻找到一个节点的安全路径时,肯定有一条非树边连接在那个节点上。而又由于要求长度最小,所以整个安全路径只存在一条非树边。这样的话就好展开情况讨论了。

补:安全路径并非次短路,因为次短路也有可能经过不能通过的那条边。

(由于画的丑而且懒,就不补图了)

  • 目标点(A)为叶子结点——找到所有以A为端点的线和与A相连的点,答案就为\(min(dis[x]+d)\))(d为边权)

    \(Ans=min(dis[x]+d+dis[y])-dis[A]\)(此情况下\(A=y\)

  • 目标点为非叶节点:

    需要分两种情况——

    • 从子树中节点走到自己:直接找通向子孙的最短路径。所以可以直接从上一种情况继承长度。\(Ans=min(dis[x]+d+dis[y])-dis[A]\)(y为子树中节点)
    • 从其他节点走到自己:从最短路径树的构造很容易看出,肯定不能从自己的子树中绕上来,那样会消耗更多的点数。此时,就枚举所有不在自己子树中且与自己相连的点(同时也不是自己的父亲节点),并前往计算到x的最短路,再从x到A。\(Ans=min(dis[x]+d+dis[y])-dis[A]\)(此情况下\(A=y\)

至此,所有情况讨论完毕。整理得:

\(Ans=min(dis[x]+d+dis[y])-dis[A]\),x为子树外一点,y为子树内一点(包括自己).

这样的话,就跟可并堆扯上关系了。在整棵树中从下向上合并(DSU),并在计算某一节点的答案时删掉自己子树中的点。此即为最终解。

注:由于使用到了左偏树,需要像链表那样把整个多叉树转化为左儿子右兄弟(同层同父节点)的二叉树。一次DFS即可。判断是否在子树中使用DFN计数。


KONO代码哒!

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int maxn = 200010;
struct edge
{
    int to, next, dis;
} g[maxn << 1];
int head[maxn], cnt;
void add(int from, int to, int dis)
{
    g[++cnt].to = to;
    g[cnt].next = head[from];
    g[cnt].dis = dis;
    head[from] = cnt;
}
struct node
{
    int val, dis, ls, rs;
} sum[maxn << 1];
int n, m;
struct _node
{
    int dis, id;
};
bool operator<(_node a, _node b)
{
    return a.dis > b.dis;
}
priority_queue<_node> q;
int root[maxn], dfi[maxn][2], son[maxn], bro[maxn], f[maxn];
int dis[maxn];
bool mark[maxn];
int merge(int x, int y)
{
    if (!x || !y)
        return x + y;
    if (sum[x].val > sum[y].val)
        swap(x, y);
    sum[x].rs = merge(sum[x].rs, y);
    if (sum[sum[x].ls].dis < sum[sum[x].rs].dis)
        swap(sum[x].ls, sum[x].rs);
    sum[x].dis = sum[sum[x].rs].dis + 1;
    return x;
}
void dij(int x)
{
    memset(dis, 0x3f, sizeof(dis));
    dis[x] = 0;
    mark[x] = 1;
    _node tmp;
    tmp.dis = 0;
    tmp.id = x;
    q.push(tmp);
    while (!q.empty())
    {
        int u = q.top().id;
        q.pop();
        // printf("%d %d\n",u,dis[u]);
        for (int i = head[u]; i; i = g[i].next)
        {
            int v = g[i].to, d = g[i].dis;
            if (!mark[v] && dis[u] + d < dis[v])
            {
                dis[v] = dis[u] + d;
                tmp.dis = dis[v];
                tmp.id = v;
                f[v] = u;
                q.push(tmp);
            }
        }
    }
}
int DFN;
void build_dfs(int x)
{
    dfi[x][0]=++DFN;
    for(int i=son[x];i;i=bro[i]){
        build_dfs(i);
    }
    dfi[x][1]=DFN;
}
void build()
{
    for(int i=2;i<=n;i++){
        bro[i]=son[f[i]];
        son[f[i]]=i;
    }
    build_dfs(1);
}
int ans[maxn];
void pt()
{
    int i=100000000;
    while(i--);
}
void dfs(int x)
{
    // printf("%d\n",x);
    for(int i=head[x];i;i=g[i].next)
    {
        int v=g[i].to,d=g[i].dis;
        // printf("%d\n",i);
        if(v!=f[x]&&f[v]!=x){
            // printf("www");
            sum[i].dis=1;
            sum[i].val=dis[v]+d+dis[x];
            // printf("%d %d\n",v,x);
            root[x]=merge(root[x],i);
        }
    }
    // printf("www"); 
    for(int i=son[x];i;i=bro[i])
    {
        dfs(i);
        root[x]=merge(root[x],root[i]);
        // printf("%d %d\n",x,i);
        // pt();
    }
    
    int l=dfi[x][0],r=dfi[x][1];
    while(root[x])
    {
        int v=g[root[x]].to;
        // printf("%d %d\n",sum[root[x]].ls,sum[root[x]].rs);
        // pt();
        if(l<=dfi[v][0]&&dfi[v][0]<=r)
        {
            root[x]=merge(sum[root[x]].ls,sum[root[x]].rs);
        }
        else break;
    }
    ans[x]=root[x]?sum[root[x]].val-dis[x]:-1;
    // printf("--%d %d\n",x,ans[x]);
}
int main()
{
    scanf("%d%d",&n,&m);
    int i,j;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
    }
    dij(1);
    build();
    // for(i=1;i<=n;i++)printf("-%d %d\n",dfi[i][0],dfi[i][1]);
    dfs(1);
    
    for(i=2;i<=n;i++)printf("%d\n",ans[i]);
    return 0;
}

说说个人犯的错吧。

1.太久没写最短路差点忘了\(Dij\)怎么写了(捂脸

2.在合并操作时需要把边当作节点编号,不小心打成点编号了(捂脸

最后,感谢nodgd老师带来的讲解!

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!