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。 2≤n≤2×105,n−1≤m≤2×105,2≤k≤n,1≤ai≤n,1≤x,y≤n。
输入样例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之间建立一条无向边 ②、增加的边经过最短路,假设在点u和v之间建立一条无向边
则 可 能 的 解 有 三 种 情 况 : \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) Ⅱ、1→u→v→n:min_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) Ⅲ、1→v→u→n:min_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] 记i号点到1号点的最短距离为d1[i],到n号点的最短距离为d2[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,v∈aimax(d1[u]+1+d2[v]),u,v∈aimax(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],也就是说点v的层次大于点u,我们不能够建一条往回走的边。 对情况Ⅲ同理
所 以 , 我 们 的 目 标 实 际 上 是 要 先 求 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,v∈aimax(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, 若能够将所有点分层,从低层(靠近1号点的层)向高层遍历,维护d1[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=ai,我们只需考虑在v与其前面的点u=aj(j∈[1,i−1])之间建边,
那 就 可 以 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,i−1]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,v∈aimax(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 \ 恒成立 就能够确保:ANS1≤ANS2 恒成立
那 么 我 们 仅 需 计 算 情 况 Ⅱ 的 最 大 值 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;
}