Codeforces 1307D Cow and Fields

Description

给定一个有 $n$ 个节点 $m$ 条边的有向图,一个顶点集 $S$。

你需要选择两个顶点 $u,v$($u e v,uin S,vin S$)并连接这两个顶点(允许 $u,v$ 之间已经有连边),求连接后从顶点 $1$ 到顶点 $n$ 最短路的最大值。

注意,该操作仅能进行一次。

保证给定的图联通。

Solution

我们可以预处理出 $1 o u$ 的最短路记为 $dis_{1 u}$,$u o n$ 的最短路记为 $dis_{2 u}$。

这个可以通过正反各 BFS 一遍得到。

接下来介绍两种不同的解法。

Solution1

对于 $u$,必须经过它 的最短路长度显然是 $dis_{1 u} + dis_{2 u}$。

现在,我们在 $u, v$ 之间添加了一条边。

那么,设想 $dis_{1 u}$ 的求解过程,它是可以从任何一个和 $u$ 相邻的点转移过来的,这些转移点中显然不包含 $v$。那么现在无非就是多了一种从 $v$ 转移的情况,也就是说,$dis_{1 u} gets min(dis_{1 u}, dis_{1, v} + 1)$。

如果原来 $dis_{1 u} le dis_{1, v} + 1$,那么这个转移就没意义了,所以只考虑有意义的情况。显然,$dis_{2 u}$ 是不变的,所以,新的最短路长度就是

$$ dis_{1 v} + 1 + dis_{2 u} $$

算出缩短的长度:

$Delta = (dis_{1 u} + dis_{2 u}) - (dis_{1 v} + 1 + dis_{2 u})$
$quad = dis_{1 u} - dis_{1 v} - 1$

想要让 $Delta$ 尽可能小,显然,我们选择的 $v$ 应当是满足 $dis_{1 u} - dis_{1 v}$ 最小的。而别忘了这里的条件是 $dis_{1 v} + 1 le dis_{1 u}$,也就是说,$dis_{1 v} < dis_{1 u}$。所以按照 $dis_1$ 对 $a$ 排序,边就一定是建在 $a_{i -1}$ 和 $a_i$ 之间的啦!

有人会问,$dis_{1 a_i} = dis_{1 a_{i -1}}$,不就不满足了吗?

没错,但是答案只和 $dis_1$ 的值有关,和 $a_i$ 无关,即便这里出现了一个无意义的建边,但是 $a_{i-1}$ 和它前面建的边,不就相当于 $a_i$ 和 $a_{i-1}$ 前面建的边吗?

时间复杂度 $mathcal O(n log n)$。

#include <bits/stdc++.h>
#define rep(i, x, y) for(register int i = x; i < y; i++)
#define REP(i, x, y) for(register int i = x; i <= y; i++)
using namespace std;
const int N = 2e5 + 5;
int n, m, k, ans;
int a[N], dis1[N], dis2[N];
vector<int> G[N];
queue<int> que;
inline bool cmp(int x, int y) { return dis1[x] < dis1[y]; }
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m >> k;
    REP(i, 1, k) cin >> a[i];
    REP(i, 1, m)
    {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    memset(dis1, -1, sizeof dis1);
    dis1[1] = 0;
    que.push(1);
    while(!que.empty())
    {
        int u = que.front();
        que.pop();
        for(int v : G[u]) if(dis1[v] == -1)
        {
            dis1[v] = dis1[u] + 1;
            que.push(v);
        }
    }
    memset(dis2, -1, sizeof dis2);
    dis2[n] = 0;
    que.push(n);
    while(!que.empty())
    {
        int u = que.front();
        que.pop();
        for(int v : G[u]) if(dis2[v] == -1)
        {
            dis2[v] = dis2[u] + 1;
            que.push(v);
        }
    }
    sort(a + 1, a + k + 1, cmp);
    ans = 0;
    rep(i, 1, k) ans = max(ans, min(dis1[n], min(dis1[a[i]] + dis2[a[i + 1]], dis1[a[i + 1]] + dis2[a[i]]) + 1));
    cout << ans << endl;
    return 0;
}

Solution 2

比如我们的边添加在 $u, v$ 之间。

一条 必须经过 $left< u, v ight>$ 的 $1 o n$ 路径长度怎么表示(不经过那么这条边就没意义了)?

$$ min(dis_{1 u} + dis_{2 v} + 1, dis_{1 v} + dis_{2 u} + 1)$$

原来最短路的长度显然是 $dis_{1 n}$,然后分类讨论一下。

1. 如果 $dis_{1 u} + dis_{2 v} + 1 le dis_{1 v} + dis_{2 u} + 1$

这时,新路径的长度为 $dis_{1 u} + dis_{2 v} + 1$。

路径的缩短长度 $Delta = dis_{1 n} - (dis_{1 u} + dis_{2 v} + 1)$,我们想要 $Delta$ 尽可能小。

$Delta = dis_{1 n} - dis_{1 u} - dis_{2 v} - 1$
$quad = dis_{1 n} - 1 - (dis_{1 u} + dis_{2 v})$

显然,$(dis_{1 u} + dis_{2 v})$ 取到 $max$ 时,$Delta$ 最小。

2. 如果 $dis_{1 u} + dis_{2 v} + 1 ge dis_{1 v} + dis_{2 u} + 1$

这时,新路径的长度为 $dis_{1 v} + dis_{2 u} + 1$。

路径的缩短长度 $Delta = dis_{1 n} - (dis_{1 v} + dis_{2 u} + 1)$,我们想要 $Delta$ 尽可能小。

$Delta = dis_{1 n} - dis_{1 v} - dis_{2 u} - 1$
$quad = dis_{1 n} - 1 - (dis_{1 v} + dis_{2 u})$

显然,$(dis_{1 v} + dis_{2 u})$ 取到 $max$ 时,$Delta$ 最小。

贪心吗?注意,我们虽然是想要一个东西取到 $max$,但是这是两种情况,我们满足了一种情况的 $max$ 时很可能会发现它的条件并不满足这种情况。

所以,排序一遍直接输出的想法告吹了 QwQ。

那么只好在满足条件的范围内取值。

第一种情况的条件移项后其实是什么?$dis_{1 u} - dis_{2 u} le dis_{1 v} - dis_{2 v}$。

第二种情况呢?$dis_{1 u} - dis_{2 u} > dis_{1 v} - dis_{2 v}$。

所以,我们只要把 $a$ 按照 $dis_1 - dis_2$ 升序排序,枚举一个 $a_i$ 作为 $v$,它前面的都是第一种情况,后面的都是第一种情况。所以,维护 前缀 $dis_1 max$ 作为 $u$,和 后缀 $dis_2 max$ 作为 $u$,都试一下就行了!

时间复杂度也是 $mathcal O(n log n)$。

#include <bits/stdc++.h>
#define rep(i, x, y) for(register int i = x; i < y; i++)
#define REP(i, x, y) for(register int i = x; i <= y; i++)
#define PER(i, x, y) for(register int i = x; i >= y; i--)
using namespace std;
const int N = 2e5 + 5;
int n, m, k, ans;
int a[N], dis1[N], dis2[N], pre[N], suf[N];
vector<int> G[N];
queue<int> que;
inline bool cmp(int x, int y) 
{
    return dis1[x] - dis2[x] < dis1[y] - dis2[y]; 
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m >> k;
    REP(i, 1, k) cin >> a[i];
    REP(i, 1, m)
    {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    memset(dis1, -1, sizeof dis1);
    dis1[1] = 0;
    que.push(1);
    while(!que.empty())
    {
        int u = que.front();
        que.pop();
        for(int v : G[u]) if(dis1[v] == -1)
        {
            dis1[v] = dis1[u] + 1;
            que.push(v);
        }
    }
    memset(dis2, -1, sizeof dis2);
    dis2[n] = 0;
    que.push(n);
    while(!que.empty())
    {
        int u = que.front();
        que.pop();
        for(int v : G[u]) if(dis2[v] == -1)
        {
            dis2[v] = dis2[u] + 1;
            que.push(v);
        }
    }
    sort(a + 1, a + k + 1, cmp);
    REP(i, 1, k) pre[i] = max(pre[i - 1], dis1[a[i]]);
    PER(i, k, 1) suf[i] = max(suf[i + 1], dis2[a[i]]);
    REP(i, 1, k)
    {
        if(i > 1) ans = max(ans, min(dis1[n], dis2[a[i]] + 1 + pre[i - 1]));
        if(i < k) ans = max(ans, min(dis1[n], dis1[a[i]] + 1 + suf[i + 1]));
    }
    cout << ans << endl;
    return 0;
}
原文地址:https://www.cnblogs.com/syksykCCC/p/CF1307D.html