Codeforces 1307D. Cow and Fields

Binary search is USEFUL!

题目链接:1307D - Cow and Fields

题目大意:给定一个 \(n\) 个点 \(m\) 条边的简单无向连通图,并指定 \(k\) 个特殊点。要求在特殊点之间加一条边使得从 \(1\)\(n\) 的最短路最大,输出这个最大值。

一般这种题有一个通用的套路就是以起点和终点为源点各跑一次单源最短路,在这题里我们直接 \(\texttt{BFS}\) 即可。这样我们就能对每个点 \(i\) 求出其到点 \(1\) 的距离 \(dis(1,i)\) 与到点 \(n\) 的距离 \(dis(n,i)\)。接着我们就需要考虑加入一条边 \((s,t)\) 产生的贡献,其为 min(dis[1][s]+dis[t][n],dis[1][t]+dis[s][n])+1。我们的目的就是最大化这个贡献,这样就能让结果的最短路尽可能大。这里我们采用一个 useful 的算法,也就是二分来解决这个问题。

二分贡献的值 \(w\),那么我们要做的就是判断是否存在 \(s,t\) 使得 min(dis[1][s]+dis[t][n],dis[1][t]+dis[s][n])+1>=w 成立,那么就是要让括号内的两项同时大于等于 \(w-1\)。我们进行移项,会得到我们要判断的式子实际上就是:

\[\begin{cases} dis(t,n)\ge w-dis(s,1)-1\\ dis(t,1)\ge w-dis(s,n)-1\\ \end{cases} \]

那么我们枚举 \(s\),把 \(dis(t,n)\)\(dis(t,1)\) 看成与 \(t\) 有关的两个数组 \(a,b\)。就变成了每次要判断是否存在 \(t\) 使得 a[t]>=w1 && b[t]>=w2 成立。

那么采用求解二维偏序同样的技巧,我们在枚举 \(s\) 的时候,按照 \(dis(s,1)\) 升序的规律枚举,并同样地把 \(t\) 按照 \(dis(t,n)\) 降序排列。这样就能做到每次询问时,\(w_1\) 的值是单调下降的,那么就使用双指针,把满足 $a_t\ge w_1 $ 的 \(t\) 对应的 \(b_t\) 加进来,之后再判断是否存在 $b_t \ge w_2 $ 即可。使用树状数组维护的话时间复杂度为 \(O(n\log ^2n)\),虽然是双 \(\log\) 但是跑得飞快。需要注意的是由于不能加一个自环进去,所以需要在枚举到 \(s\) 时要暂时把 \(b_s\) 的贡献去掉(如果此时 \(b_s\) 被加到了树状数组内)。

但注意到,实际上我们最后只需要判断一个存在性,所以只需要在过程中维护 \(b_t\) 的最大值和次大值(为了维护去掉 \(b_s\) 的情况)即可,时间复杂度 \(O(n\log n)\)。虽然实践证明加了这个优化后反而跑得更慢了(草。

\(O(n\log ^2n)\)的代码如下,不要忘了最后要和原本的最短路取 \(\min\)

#include<bits/stdc++.h>
using namespace std;
#define N 200020
#define mp make_pair
int n,m,k,dis[N][2];
vector<int>d[N],t,e;
struct BIT
{
    int t[N],cnt;
    int lowbit(int x){return x&(-x);}
    void init(){cnt=0;memset(t,0,sizeof(t));}
    void cg(int x,int c){cnt+=c;while(x<N)t[x]+=c,x+=lowbit(x);}
    int ask(int x){int r=0;while(x)r+=t[x],x-=lowbit(x);return r;}
    int Q(int x){return ask(max(x-1,0))<cnt;}
}T;
bool cmp(int x,int y){return dis[x][0]<dis[y][0];}
bool cmp2(int x,int y){return dis[x][1]>dis[y][1];}
void BFS(int st,int o)
{
    queue<int>q;
    q.push(st);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(auto y:d[x])if(!dis[y][o] && y!=st){
            dis[y][o]=dis[x][o]+1;
            q.push(y);
        }
    }
}
bool check(int w)
{
    int i=0;
    T.init();
    for(auto s:t){
        int w1=w-dis[s][0]-1;
        while(i<k && dis[e[i]][1]>=w1)
            T.cg(dis[e[i]][0]+1,1),i++;
        if(dis[s][1]>=w1)T.cg(dis[s][0]+1,-1);
        if(T.Q(w-dis[s][1]))return true;
        if(dis[s][1]>=w1)T.cg(dis[s][0]+1,1);
    }
    return false;
}
int Useful()
{
    int l=1,r=dis[n][0],mid;
    while(l<r){
        mid=l+r+1>>1;
        if(check(mid))l=mid;
        else r=mid-1;
    }
    return l;
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=k;i++){
        int x;
        scanf("%d",&x);
        t.push_back(x);
        e.push_back(x);
    }
    for(int i=1;i<=m;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        d[u].push_back(v);
        d[v].push_back(u);
    }
    BFS(1,0),BFS(n,1);
    sort(t.begin(),t.end(),cmp);
    sort(e.begin(),e.end(),cmp2);
    printf("%d\n",min(dis[1][1],Useful()));
}

名言警句:Stop learning useless algorithms, go and solve some problems, learn how to use binary search.

posted @ 2022-07-22 22:33  DeaphetS  阅读(47)  评论(0编辑  收藏  举报