BFS + 贪心 - 最大化最短路 - acw 3797

BFS + 贪心 - 最大化最短路 - acw 3797

题意:

给定一个 n 个点 m 条边的无向连通图。

图中所有点的编号为 1∼n。

图中不含重边和自环。

指定图中的 k 个点为特殊点。

现在,你必须选择两个特殊点,并在这两个点之间增加一条边。

所选两点之间允许原本就存在边。

我们希望,在增边操作完成以后,点 1 到点 n 的最短距离尽可能大。

输出这个最短距离的最大可能值。

注意,图中所有边(包括新增边)的边长均为 1。

输入格式

第一行包含三个整数 n,m,k。

第二行包含 k 个整数 a 1 , a 2 , … , a k a_1,a_2,…,a_k a1,a2,,ak,表示 k 个特殊点的编号, a i a_i ai 之间两两不同。

接下来 m 行,每行包含两个整数 x,y,表示点 x 和点 y 之间存在一条边。

输出格式

一个整数,表示最短距离的最大可能值。

数据范围

2 ≤ n ≤ 2 × 1 0 5 , n − 1 ≤ m ≤ 2 × 1 0 5 , 2 ≤ k ≤ n , 1 ≤ a i ≤ n , 1 ≤ x , y ≤ n 。 2≤n≤2×10^5,n−1≤m≤2×10^5,2≤k≤n,1≤a_i≤n,1≤x,y≤n。 2n2×105n1m2×1052kn1ain1x,yn

输入样例1:

5 5 3
1 3 5
1 2
2 3
3 4
3 5
2 4

输出样例1:

3

输入样例2:

5 4 2
2 4
1 2
2 3
3 4
4 5

输出样例2:

3

分析:

仔 细 思 考 , 会 发 现 : 增 加 一 条 边 只 会 使 得 最 短 路 不 变 或 更 短 。 仔细思考,会发现:增加一条边只会使得最短路不变或更短。 使

详 细 讨 论 加 边 后 的 情 况 ( 划 分 解 空 间 ) : 详细讨论加边后的情况(划分解空间):

① 、 增 加 的 边 不 是 原 最 短 路 上 的 边 , 则 答 案 还 是 原 最 短 路 。 ①、增加的边不是原最短路上的边,则答案还是原最短路。

② 、 增 加 的 边 经 过 最 短 路 , 假 设 在 点 u 和 v 之 间 建 立 一 条 无 向 边 ②、增加的边经过最短路,假设在点u和v之间建立一条无向边 uv

则 可 能 的 解 有 三 种 情 况 : \qquad 则可能的解有三种情况:

Ⅰ 、 原 最 短 路 \qquad Ⅰ、原最短路

Ⅱ 、 1 → u → v → n : m i n _ d i s t a n c e ( 1 ,   u ) + 1 + m i n _ d i s t a n c e ( v , n ) \qquad Ⅱ、1\rightarrow u\rightarrow v\rightarrow n: min\_distance(1,\ u) + 1 + min\_distance(v,n) 1uvnmin_distance(1, u)+1+min_distance(v,n)

Ⅲ 、 1 → v → u → n : m i n _ d i s t a n c e ( 1 ,   v ) + 1 + m i n _ d i s t a n c e ( u , n ) \qquad Ⅲ、1\rightarrow v\rightarrow u\rightarrow n: min\_distance(1,\ v) + 1 + min\_distance(u,n) 1vunmin_distance(1, v)+1+min_distance(u,n)

记 i 号 点 到 1 号 点 的 最 短 距 离 为 d 1 [ i ] , 到 n 号 点 的 最 短 距 离 为 d 2 [ i ] 记i号点到1号点的最短距离为d_1[i],到n号点的最短距离为d_2[i] i1d1[i]nd2[i]

则 : 则:

a n s = m i n ( d 1 [ n ] , m a x u , v ∈ a i ( d 1 [ u ] + 1 + d 2 [ v ] ) , m a x u , v ∈ a i ( d 1 [ v ] + 1 + d 2 [ u ] )   ) ans=min(d_1[n],\underset{u,v\in a_i}{max}(d_1[u]+1+d_2[v]),\underset{u,v\in a_i}{max}(d_1[v]+1+d_2[u])\ ) ans=min(d1[n],u,vaimax(d1[u]+1+d2[v]),u,vaimax(d1[v]+1+d2[u]) )

注意: 对 于 情 况 Ⅱ , 需 满 足 d 1 [ v ] > d 1 [ u ] , 也 就 是 说 点 v 的 层 次 大 于 点 u , 我 们 不 能 够 建 一 条 往 回 走 的 边 。     对 情 况 Ⅲ 同 理 对于情况Ⅱ,需满足d_1[v] > d_1[u],也就是说点v的层次大于点u,我们不能够建一条往回走的边。\\\qquad\ \ \ 对情况Ⅲ同理 d1[v]>d1[u]vu   

所 以 , 我 们 的 目 标 实 际 上 是 要 先 求 m a x u , v ∈ a i ( d 1 [ u ] + 1 + d 2 [ v ] ) , 且 d 1 [ v ] > d 1 [ u ] 所以,我们的目标实际上是要先求\underset{u,v\in a_i}{max}(d_1[u]+1+d_2[v]),且d_1[v]>d_1[u] u,vaimax(d1[u]+1+d2[v])d1[v]>d1[u]

若 直 接 O ( k 2 ) 枚 举 , 必 然 不 行 。 若直接O(k^2)枚举,必然不行。 O(k2)

若 能 够 将 所 有 点 分 层 , 从 低 层 ( 靠 近 1 号 点 的 层 ) 向 高 层 遍 历 , 维 护 d 1 [ u ] 的 最 大 值 d i s 1 , 若能够将所有点分层,从低层(靠近1号点的层)向高层遍历,维护d_1[u]的最大值dis1, 1d1[u]dis1

那 对 于 当 前 遍 历 到 的 点 v = a i , 我 们 只 需 考 虑 在 v 与 其 前 面 的 点 u = a j ( j ∈ [ 1 , i − 1 ] ) 之 间 建 边 , 那对于当前遍历到的点v=a_i,我们只需考虑在v与其前面的点u=a_j(j\in[1,i-1])之间建边, v=aivu=ajj[1,i1]

那 就 可 以 O ( k ) 的 时 间 复 杂 度 更 新 答 案 : a n s = m a x j = a i , i ∈ [ 1 , k ] ( d i s 1 + d 2 [ j ] + 1 ) , 其 中 d i s 1 = m a x u ∈ [ 1 , i − 1 ] ( d 1 [ a u ] ) 那就可以O(k)的时间复杂度更新答案:ans=\underset{j=a_i,i\in[1,k]}{max}(dis1+d_2[j]+1),其中dis1=\underset{u\in [1,i-1]}{max}(d_1[a_u]) O(k)ans=j=ai,i[1,k]max(dis1+d2[j]+1)dis1=u[1,i1]max(d1[au])

Q:如何分层?

将 所 有 点 按 照 距 离 1 号 点 的 距 离 排 序 即 可 。 将所有点按照距离1号点的距离排序即可。 1

然 后 我 们 取 得 情 况 Ⅱ 的 答 案 , 记 为 A N S 1 。 然后我们取得情况Ⅱ的答案,记为ANS_1。 ANS1

对 于 情 况 Ⅲ : m a x u , v ∈ a i ( d 1 [ v ] + 1 + d 2 [ u ] ) , 同 理 可 计 算 出 答 案 , 记 为 A N S 2 。 对于情况Ⅲ:\underset{u,v\in a_i}{max}(d_1[v]+1+d_2[u]),同理可计算出答案,记为ANS_2。 u,vaimax(d1[v]+1+d2[u])ANS2

则 最 终 的 答 案 为 : 则最终的答案为:

a n s = m i n ( d 1 [ n ] , A N S 1 , A N S 2 ) ans=min(d_1[n],ANS_1,ANS_2) ans=min(d1[n],ANS1,ANS2)

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;

const int N = 200010, M = N * 2, inf = 0x3f3f3f3f;

int n, m, k;
int e[M], ne[M], h[N], idx;
int q[N], d1[N], d2[N], a[N];
int ans = inf;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

void bfs(int S, int d[])
{
    memset(d, 0x3f, sizeof d1);
    d[S] = 0; 
    int hh = 0, tt = -1;
    q[++ tt] = S;
    
    while(hh <= tt)
    {
        int u = q[hh ++];
        
        for(int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            
            if(d[v] > d[u] + 1)
            {
                d[v] = d[u] + 1;
                q[++ tt] = v;
            }
        }
    }
}

void solve(int d1[], int d2[])
{
    sort(a, a + k, [&](int a, int b){
        return d1[a] < d1[b];
    });
    
    int res = 0, dis1 = d1[a[0]];
    for(int i = 1; i < k; i ++)
    {
        int j = a[i];
        res = max(res, dis1 + d2[j] + 1);
        dis1 = max(dis1, d1[j]);
    }

    ans = min(res, ans);
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 0; i < k; i ++) scanf("%d", &a[i]);
    
    memset(h, -1, sizeof h);
    int u, v;
    while(m --)
    {
        scanf("%d%d", &u, &v);
        add(u, v), add(v, u);
    }

    bfs(1, d1);
    bfs(n, d2);
    
    solve(d1, d2);
    solve(d2, d1);
    ans = min(ans, d1[n]);
    
    printf("%d\n", ans);

    return 0;
}

优化:

事 实 上 , 我 们 可 以 通 过 一 些 性 质 进 行 排 序 , 仅 需 计 算 情 况 Ⅱ , 排 除 情 况 Ⅲ 。 事实上,我们可以通过一些性质进行排序,仅需计算情况Ⅱ,排除情况Ⅲ。

我 们 只 需 按 照 如 下 的 性 质 对 特 殊 点 集 进 行 排 序 :   d 1 [ u ] + d 2 [ v ] ≤   d 1 [ v ] + d 2 [ u ] 我们只需按照如下的性质对特殊点集进行排序:\ d_1[u]+d_2[v] \le\ d_1[v]+d_2[u]  d1[u]+d2[v] d1[v]+d2[u]

就 能 够 确 保 : A N S 1 ≤ A N S 2   恒 成 立 就能够确保:ANS_1\le ANS_2 \ 恒成立 ANS1ANS2 

那 么 我 们 仅 需 计 算 情 况 Ⅱ 的 最 大 值 A N S 1 即 可 那么我们仅需计算情况Ⅱ的最大值 ANS_1即可 ANS1

但是:

我 们 需 要 考 虑 , 这 样 排 序 后 , 所 有 的 点 能 否 按 照 层 次 排 好 序 ?   即 当 我 们 顺 序 遍 历 数 组 时 , 是 否 满 足 : 序 号 在 前 的 点 的 层 次 ≤ 序 号 在 后 的 点 的 层 次 ? 我们需要考虑,这样排序后,所有的点能否按照层次排好序?\\ \ \\即当我们顺序遍历数组时,是否满足:序号在前的点的层次\le序号在后的点的层次?  

先挖个坑,等弄明白了再来继续写

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;

const int N = 200010, M = N * 2, inf = 0x3f3f3f3f;

int n, m, k;
int e[M], ne[M], h[N], idx;
int q[N], d1[N], d2[N], a[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

void bfs(int S, int d[])
{
    memset(d, 0x3f, sizeof d1);
    d[S] = 0; 
    int hh = 0, tt = -1;
    q[++ tt] = S;
    
    while(hh <= tt)
    {
        int u = q[hh ++];
        
        for(int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            
            if(d[v] > d[u] + 1)
            {
                d[v] = d[u] + 1;
                q[++ tt] = v;
            }
        }
    }
}

void solve()
{
    sort(a, a + k, [&](int a, int b){
        return d1[a] - d2[a] < d1[b] - d2[b];
    });
    
    int res = 0, dis1 = d1[a[0]];
    for(int i = 1; i < k; i ++)
    {
        int j = a[i];
        res = max(res, dis1 + d2[j] + 1);
        dis1 = max(dis1, d1[j]);
    }
    res = min(d1[n], res);
    
    printf("%d\n", res);
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 0; i < k; i ++) scanf("%d", &a[i]);
    
    memset(h, -1, sizeof h);
    int u, v;
    while(m --)
    {
        scanf("%d%d", &u, &v);
        add(u, v), add(v, u);
    }

    bfs(1, d1);
    bfs(n, d2);
    
    solve();

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值