写在前面
再次快读打炸得我。。
题目
题解
我们考虑简化问题,对于长度小于二分出的答案的线段,因为不需要付价钱,所以可以将其权值看作是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;
}