分析
题目就是要我们求所有从 $1\to n$ 的路径中第 $k+1$ 大的边的最小值。
首先考虑到搜出所有路径再找最小值,但发现数据规模太大,做不了。
于是我们可以从最小值入手,也就是采取二分答案,利用二分将这个值求出来。
假设我们二分出一个值x
,那意味着x
应该满足:从 $1 \to n$ 的路径中应该存在一条路,使得这条路上最多有 $k$ 条大于x
的边。(二分值需要满足的属性)
那么如何刻画这个属性呢?
可以着眼于图上的边权是否大于x
,如果大于,则贡献是 $1$,否则是 $0$,那么题目就转化为最短路问题。这里采用 $\texttt{dijkstra}$ 算法来求。
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=1005,M=1e4+5;
struct node{
int to,w,next;
}e[M<<1];
int head[N],tot;
void add(int u,int v,int w){e[tot].to=v;e[tot].w=w;e[tot].next=head[u];head[u]=tot++;}
int d[N];
bool vis[N];
int n,m,k;
bool dijk(int w){
memset(d,0x3f,sizeof d);
memset(vis,false,sizeof vis);
d[1]=0;
priority_queue<PII,vector<PII>,greater<PII> > pque;
pque.push({0,1});
while(pque.size()){
auto hd=pque.top(); pque.pop();
int ver=hd.second;
if(vis[ver]) continue;
vis[ver]=true;
for(int i=head[ver];~i;i=e[i].next){
int go=e[i].to;
if(d[go]>d[ver]+(e[i].w>w?1:0)){ // 如果是,贡献就是1
d[go]=d[ver]+(e[i].w>w?1:0);
pque.push({d[go],go});
}
}
}
return d[n]<=k;
}
int main(){
memset(head,-1,sizeof head);
cin>>n>>m>>k;
while(m--){
int u,v,w;
cin>>u>>v>>w;
add(u,v,w); add(v,u,w);
}
//binary search
int l=0,r=1e6+1; //这里有个技巧,因为题目边的范围在1e6内,如果二分答案超过它,说明不存在1->n的路径
while(r>l){
int mid=r+l>>1;
if(dijk(mid)) r=mid;
else l=mid+1;
}
if(r!=1e6+1) cout<<r<<endl;
else cout<<-1<<endl;
return 0;
}
UPD:2022. 8. 22
非常感谢 @备战NOIP版的XXR 指出可以使用双端队列优化一个 $log$:
下面提供双端队列版本的代码,可以发现效率高了不少。
#include<bits/stdc++.h>
using namespace std;
const int N=1005, M=1e4+5;
struct Edge{
int to, w, next;
}e[M<<1];
int h[N], tot;
void add(int u, int v, int w){
e[tot].to=v, e[tot].w=w, e[tot].next=h[u], h[u]=tot++;
}
int d[N];
bool vis[N];
int n, m, k;
bool dijk(int lim){
memset(d, 0x3f, sizeof d);
memset(vis, false, sizeof vis);
d[1]=0;
deque<int> q;
q.push_back(1);
while(q.size()){
int u=q.front(); q.pop_front();
if(vis[u]) continue;
vis[u]=true;
for(int i=h[u]; ~i; i=e[i].next){
int go=e[i].to;
if(d[go]>d[u]+(e[i].w>lim)){
d[go]=d[u]+(e[i].w>lim);
if(e[i].w>lim) q.push_back(go);
else q.push_front(go);
}
}
}
return d[n]<=k;
}
int main(){
memset(h, -1, sizeof h);
cin>>n>>m>>k;
while(m--){
int u, v, w; cin>>u>>v>>w;
add(u, v, w), add(v, u, w);
}
//binary search
int l=0, r=1e6+1;
while(r>l){
int mid=r+l>>1;
if(dijk(mid)) r=mid;
else l=mid+1;
}
if(r!=1e6+1) cout<<r<<endl;
else cout<<-1<<endl;
return 0;
}
看了你的终于会做了
上面是dijkstra,下面是spfa吧
下面是双端队列😄
我有个疑问就是当二分枚举的答案能在1到N的路径中找到<= k的边数,此时该答案应该是可以继续二分的,但如果
所有边中都没有为x的边权这样是不是二分从本质上就错了
我有这个疑问,然后因为我写错了输出,输出了mid结果比原本答案的小了1,我以为是因为我找到的这个边原本就不存在于图中。虽然最后找到了错的是写成输出mid了,但是我感觉确实会有这种可能,就是我们找到了一个不存在图中的一个最终边权,但是又有小于等于k个边比他大的。然后我们就会错误的输出这个不存在的边权。
一定会有的
对
但是,你做二分的题目时肯定是从所有可能的情况来取l和r
看了您的思路写了份代码,但是在第三个点WA了QAQ
找半天找不到问题QwQ
大佬能帮我看看吗Orz
双端队列优化会不会更快?
这样省去了一个logN
可以的,效率高了不少,已在题解更新
deque
的做法,谢谢指出😄/jk
想问下二分出来的值不是路径上的值怎么办呀
没关系,因为最后二分出来的结果一定是路径上的值
直接把所有边权排序,直接对边权二分也行
为什么一定是路径上的值呀
肥肠nice看了你的立马会了
强orz
朴素版写法 为什么不行呀?
帮你调过了:
需要注意,一个点必须要与另一个点联通才能去更新它的距离。
见代码:
orz 谢谢你!
tql,你是讲的最清楚的