USACO Jan08 (COGS 174) 架设电话线 二分答案,缩点,BFS判断可行性

时间:2021-11-04 19:32:20

这道题让我好好地加深了一下对二分的细节性处理,等会再做反思,先说一下这道题的思路。
先将题意转化一下,最多K条边免费,路径上的最大值为花费,令这个花费最小,K次免费机会当然是尽量用完啦(用不完的时候答案就是0嘛),总结来说就是求1到n的所有路径中,第K+1大的边权的最小值,若有路径上的边数不超过K,答案即0,若没有路径,答案即-1。
看到数据范围只有1000,就可以联想到这道题不会很简单,稍微复杂一点(对于我来说)。
想到二分,二分之后判断可行性,可以直接二分答案,我的做法是把所有边按边权排序,二分的是排序后的编号,这道题的一个还不错的特点就是二分之后答案为mid,可以把小于或等于mid权值的边连的点缩点(因为我二分的是编号,所以缩点的过程比较方便了,直接循环1到mid,令每条边连的点的并查集树根相同就可以),缩点可以用并查集来写(为了方便,最好让1和n作为树根),之后剩下大于mid的边(如果有大于mid的边在一个缩点内就可以忽略它),在这些剩下的边中用BFS求1到n的最短路,如果不大于K,就说明mid是可行的,反之就不可行。
其实也可以不缩点,要理解二分的答案的实际意义,我们假设答案为mid,那么权值小于或等于mid的边是没有任何影响的,想怎么走就怎么走,我们把免费的K次机会用到了大于mid的边上,在那些“想怎么走就怎么走”的边中,mid作为答案,是最大值。
另外,有无解的情况,我的做法是在二分之前先用BFS判断是否有1到n的路径,有些人省略了这一步,直接用二分答案的结果判断,蒟蒻我一开始也想尝试,发现怎么都A不掉就果断放弃了,还是用自己的提前判断吧,不过这样也速度更快吧。
这道题的思路还是很好写的,不过一开始因为自己对二分的理解不够彻底导致WA了几次。首先针对这道题,有一个点答案为0,而我一开始的输出是1,因为我把l初值设为1,之后我的解决方法是在输出答案前加了特判,之后才发现只要把l的初值设为0就可以。
另外,针对二分这个思想的就是,判断了当前答案可行后,令r = mid,不可行的话,l = mid+1,这样一来,二分循环的条件就是while(l < r)输出的答案就直接是l或r了,因为最后l == r。至于我原来逗B的写法就不论述了,太心酸了。写下这句话提醒下自己曾经是多么2。
下附代码:

#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;

int n, p, k, f[1005], d[1005];
vector <int> G[1005];
queue <int> q;
struct edge{
    int u, v, l;
}E[10005];
bool cmp(edge a, edge b){return a.l < b.l;}

int find(int x) {return f[x] = f[x] == x ? x : find(f[x]);}

int main()
{
    scanf("%d %d %d", &n, &p, &k);
    for(int i = 1; i <= p; i++)
        scanf("%d %d %d", &E[i].u, &E[i].v, &E[i].l);   
    for(int i = 1; i <= p; i++){
        int u = E[i].u, v = E[i].v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    q.push(1);
    while(q.size()){
        int i = q.front(); q.pop();
        for(int j = 0; j < G[i].size(); j++){
            int y = G[i][j];
            if(!d[y]){
                d[y] = d[i] + 1;
                q.push(y);
            }
        }
    }
    if(!d[n]){
        printf("-1");
        return 0;
    }
    sort(E+1, E+p+1, cmp);
    int l = 0, r = p, mid;
    while(l < r){
        mid = (l+r)/2;
        memset(G, 0, sizeof G);
        memset(d, 0, sizeof d);
        for(int i = 1; i <= n; i++) f[i] = i;
        for(int i = 1; i <= mid; i++) {
            int fu = find(E[i].u), fv = find(E[i].v);
            if(fu != fv) {
                if(fu == 1 || fu == n) f[fv] = fu;
                else if(fv == 1 || fv == n) f[fu] = fv;
                else f[fu] = fv;    
            }
        }
        for(int i = mid+1; i <= p; i++){
            int u = find(E[i].u), v = find(E[i].v);
            if(u == v) continue;
            G[u].push_back(v);
            G[v].push_back(u);
        }
        q.push(1);
        while(q.size()){
            int i = q.front(); q.pop(); 
            for(int j = 0; j < G[i].size(); j++){
                int y = G[i][j];
                if(!d[y] && y != 1){
                    d[y] = d[i] + 1;
                    q.push(y);
                }
            }
        }
        if(d[n] <= k) r = mid;
        else l = mid+1;
    }
    printf("%d", E[l].l);
    return 0;
}