[USACO08JAN]电话线Telephone Lines

写在前面

再次快读打炸得我。。
在这里插入图片描述

题目

传送门

题解

我们考虑简化问题,对于长度小于二分出的答案的线段,因为不需要付价钱,所以可以将其权值看作是0;同理,大于二分的值的路径,我们将长度看作1(意味着我需要使用1次免费的资格)。so,我们跑一遍spfa,看到了n点的最短路的长度,如果大于k,则不行,缩小r范围继续二分;如果小于,则有可能更小,缩小l范围继续二分

code

spfa + 二分

#include <algorithm>
#include <cctype>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstring>
#include <deque>
#include <functional>
#include <list>
#include <map>
#include <iomanip>    
#include <iostream>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;
const int maxn = 1e5 + 100;
typedef long long ull;

inline int read() {
	int s = 0, w = 1;
	char ch = getchar();
	while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); }
	while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
	return s * w;
}

int n, p, k;
struct Edge{
	int next;
	int w;
	int to;
	int dis;
}edge[maxn];
int num_edge = 0;
int l, r, mid;
int ans;
int head[maxn], dis[maxn];
bool vis[maxn];
queue<int> q;

inline void add(int from, int to, int dis) {
	edge[++num_edge].to = to;
	edge[num_edge].dis = dis;
	edge[num_edge].next = head[from];
	head[from] = num_edge;
}

inline bool spfa(int x) {
	while (!q.empty()) q.pop();
	memset(dis, 0x3f, sizeof(dis));
	memset(vis, false, sizeof(vis));
	dis[x] = 0;
	vis[x] = true;
	q.push(x);
	while (!q.empty()) {
		int u = q.front();
		vis[u] = false;
		q.pop();
		for (int i = head[u]; i; i = edge[i].next) {
			int v = edge[i].to;
			if (dis[v] > dis[u] + edge[i].w) {
				dis[v] = dis[u] + edge[i].w;
				if (!vis[v]) {
					q.push(v);
					vis[v] = true;
				}
			}
		}
	}
	return dis[n] <= k;
}

inline int check(int x) {
	for (int i = 1; i <= n; ++i) {
		for (int j = head[i]; j; j = edge[j].next) {
			if (edge[j].dis <= x) edge[j].w = 0;
			else edge[j].w = 1;
		}
	}
	return spfa(1);
}

int main() {
	r = -1; ans = -1;
	n = read(), p = read(), k = read();
	for (int i = 1; i <= p; ++i) {
		int x, y, z;
		x = read(), y = read(), z = read();
		add(x, y, z);
		add(y, x, z);
		if (z > r) r = z;
	}
	l = 0;
	while (l <= r) {
		mid = (l + r) >> 1;
		if (check(mid)) {
			ans = mid;
			r = mid - 1;	
		}
		else l = mid + 1;
	}
	printf("%d\n", ans);
	return 0;
}

bfs + 双端队列

来自石神
sro_石神博客传送门_orz

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
template<typename T>inline void read(T &x)
{
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch) && ch^'-') ch=getchar();
    if (ch=='-') f=-1, ch=getchar();
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
int ver[maxn],edge[maxn],Next[maxn],head[maxn],tot;
inline void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
}
int n,p,k,maxmum;
int dist[maxn],vis[maxn];
inline int bfs(int mid)//bfs求最短路
{
    memset(dist,0,sizeof(dist));
    memset(vis,0,sizeof(vis));
    deque<int>q;
    dist[1]=0,vis[1]=1;
    q.push_back(1);
    while (!q.empty())
    {
        int x=q.front();
        q.pop_front();
        for (int i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            if (!vis[y] || dist[y]>=dist[x]+1)
            {
                if (edge[i]<=mid)//将小于等于mid的边看做权值为零的边
                {
                    vis[y]=1,q.push_front(y);
                    dist[y]=dist[x];
                }
                else//然后将大于mid的边看做权值为1的边
                {
                    vis[y]=1,q.push_front(y);
                    dist[y]=dist[x]+1;
                }
            }
        }
    }
    if (dist[n]>k) return 0;//若最短路的长度大于k,则要连接的对数大于k对,在[mid+1,r]中继续二份查找
    else return 1;//若最短路的长度小于k,则要连接的对数比k小,在[l,mid]中继续二份查找
}
int main()
{
    read(n);read(p);read(k);
    while (p--)
    {
        int x,y,z;
        read(x);read(y);read(z);
        add(x,y,z);add(y,x,z);
        maxmum=max(maxmum,z);
    }
    int l=1,r=maxmum;
    while (l<r)//可以二分最大的花费mid,mid属于[l,r](l为最小花费,r为最大花费)
    {
        int mid=(l+r)>>1;
        if (bfs(mid)) r=mid;
        else l=mid+1;
    }
    if (l!=1) printf("%d\n",l);//最终,l即为所求
    else puts("-1");
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值