20191010修复线路

<前言>

看到有大佬们把这次考试的题都讲了一遍,见没有讲修复路线的,那我就来凑凑数吧。

<正文>

仔细读题,我们可以发现,这题是一个极为明显的最短路,而“最长的最短”这句话又告诉我们是二分答案,所以基本确定就是 最短路与二分答案 了,理解题意之后发现这是一个 二分答案用最短路验证 。根据题目选择最短路的算法,发现用 dijkstra 及其堆优化或 SPFA 合适一些,这里选用 SPFA,用邻接表存图。

因为根据二分答案的套路,判断条件应该是

if(dis[n]<=k)return 1;else return 0;

而在下面的判断中则应是

if(check(mid))r=mid;else l=mid;

也就是说如果满足了就去尝试更小的。

这个也比较好理解(吧),不会的同学还是学学二分吧。

我们二分答案所枚举的是最长的最短,但如何用最短路来验证呢?

其实很简单,我们可以再建一个图,将图中所有长度大于mid的边全部长度设为1,小于等于mid的则设置为0,;

这么做就表示长度为1的如果要连接就必须要报销;因为HL十分抠门,所以需要把长的都留给保险公司报销。

接着,开始做单源最短路:dis[] 数组存各个节点到节点1的最短路径,开始初值为一个大数(如果是 mesmet 10的话为 168430090),用 SPFA(可以用 STL 库)来做一遍第二个图上的最短路,最后判断 dis[n] 与 k 的大小关系 return 即可,操作见上代码。因为题目想连接的是 1 号机与 n 号机,所以这里 dis[n] 所代表的是“当自己接的网线最长的最短为 mid 时连接到 n 号机最少报销的网线条数”,将这个与 k 比较即可验证这个二分了。

由于我是用SPFA且是邻接表存图,代码难免有些繁琐,对于读入存图上还是有一些可有可无的玩意,我仅是提供了一个思路而已 。

但我还是要献上我的代码:

#include<bits/stdc++.h>
using namespace std;
struct rode{
	int x,y,t,next;//x表示b号机,t表示a号机,y表示网线长度,next表示下一个的地址 
}e[25001]={},b[25001]={};
int dis[1001]={};//dis数组表示各个电脑到1号机的最短路径(最少报销数) 
int f[1001]={},l=0,r=1e8,len=0,n,p,k;//f数组的意义也与邻接表的储存有关,所以还是自行理解吧; 
bool che(int mid){
	queue<int>q;//通过STL的队列来实现SPFA 
	memset(dis,10,sizeof(dis));//赋初值,这里赋的值表示168430090 
	for(int i=1;i<=len;i++){
	//	if(e[i].y<168430090) 这句其实可有可无 
		if(e[i].y<=mid)b[i].y=0;//如果长度小于二分枚举的mid,则赋为0 
		  else b[i].y=1;//否则赋为1,表示若要连接则需要报销 
		if(e[i].t==1)dis[e[i].x]=e[i].y;//先把与1号机有直接连线的赋上 
	}
	dis[1]=0;//自己到自己当然是0 
	bool p[1000001]={};//这个数组表示是否 走过 ;
	q.push(1);p[1]=1;//现将1号机入队,并标记为走过了 ;
	while(!q.empty()){//q.empty 表示判断队列是否为空,为空则返回0 
		int t=q.front();q.pop();
		p[t]=0;//走过之后归零,思考为什么 
		for(int i=f[t];i;i=b[i].next)//遍历邻接表 
		  if(b[i].y+dis[t]<dis[b[i].x]){//这个就是∨(箭头) 
			dis[b[i].x]=dis[t]+b[i].y;  //松弛操作了 
			if(p[b[i].x]==0){//若标记为没走过的则可以入队 
			  q.push(b[i].x);
			  p[b[i].x]=1;//标记为走过的 
			}
		  }
	}
	if(dis[n]==168430090){//若点n还是原值没被更新过,则说明无法走到n号机,可以直接输出-1了 
		cout<<"-1"<<endl;
		exit(0);
	}
	if(dis[n]<=k)return 1;// 上面有解释 
	 else return 0;
}
void ins(int x,int y,int z){//这个邻接表的储存方法可以百度或自行理解 
	e[++len].x=y;
	e[len].y=z;
	e[len].t=x;
	e[len].next=f[x];
	b[len].x=y;
	//注意:e数组是原图,b数组是做最短路的图,b数组在这里不能给b[len].y赋值; 
    b[len].t=x;
	b[len].next=f[x];
	f[x]=len;
}
int main()
{
	freopen("wire.in","r",stdin);
	freopen("wire.out","w",stdout);
	scanf("%d%d%d",&n,&p,&k);
	for(int i=1;i<=p;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);//分别输入a号机,b号机,网线长度 
		ins(x,y,z);ins(y,x,z);//这是无向图,相当于两条有向边 
	}
	while(l+1<r){//这是保险的方法,退出循环之后再在下面判断一次 
		int mid=(l+r)>>1;//相当于int mid=(l+r)/2; 
		if(che(mid))r=mid;//注意:这里是r=mid优先,else后面的是l 
		 else l=mid;
	}
	if(che(l))printf("%d",l);//退出后最后判一次来确定,若是弄不好则老是会差1 
	 else printf("%d",r);
	fclose(stdin);fclose(stdout);
	return 0;
}

E x t r a \mathcal{Extra} Extra

以上,是我去年写的。今天又写到这题,过来鞭尸。

槽也不吐了,去年写的太垃圾,现在补充:

  • 数据范围可到 1e6 范围,将最短路换成搜索(Bfs)之类的或排序后在权值序列中二分,都是加速的好方法。

  • 不用每次重建图,边权判断一下即可。

C o d e : \mathbf{Code:} Code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#define Rep(i, a, b) for (int i = (a); i <= (b); ++i)
const int N = 1e6 + 10; const long long inf = 1e18;
using namespace std;
int n, m, K; long long dis[N];
queue <int> q;
struct Graph { int to[N << 1], net[N << 1], w[N << 1], fl[N], len;
    inline void inc(int x, int y, int z) { return to[++len]=y,w[len]=z,net[len]=fl[x],fl[x]=len,void(); }
    inline void clear() { Rep(i, 1, len) to[i] = net[i] = w[i] = 0; Rep(i, 1, n) fl[i] = 0; len = 0; }
    inline void Inc(int x, int y, int z) { return inc(x, y, z), inc(y, x, z); }
} G;
struct Edge { int x, y, z; } e[N];
inline int read() {int s=0;char c=getchar();for(;!isdigit(c);c=getchar());for(;isdigit(c);c=getchar())s=(s<<3)+(s<<1)+c-48;return s;}
template <class T> inline void write(T x) { (x < 0 ? x = ~x + 1, putchar('-') : 0), (x > 9 ? write(x / 10) : void()); return putchar(x % 10 + 48), void(); }
inline void Bfs(int st, int mid) {
    while (q.size()) q.pop(); Rep(i, 1, n) dis[i] = inf; dis[st] = 0, q.push(st);
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int i = G.fl[u], v; v = G.to[i], i; i = G.net[i]) {
            int w = G.w[i] <= mid ? 0 : 1;
            if (dis[v] <= dis[u] + w) continue;
            dis[v] = dis[u] + w, (dis[v] <= K ? q.push(v) : void());
        }
    }
}
inline bool check(int x) { Bfs(1, x); return dis[n] <= K; }
int main(void) {
    n = read(), m = read(), K = read(); int maxn = 0;
    Rep(i, 1, m) { int x = read(), y = read(), z = read(); G.Inc(x, y, z), maxn = max(maxn, z); }
    int l = 0, r = maxn, mid; while (l + 1 < r) mid = (l + r) >> 1, (check(mid) ? r = mid : l = mid);
    m = (check(l) ? l : r), write(m >= maxn ? -1 : m); return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值