Loading

浅谈最短路应用

最短路算法除了一些基础的应用,还有一些奇奇怪怪的食用方法。

Luogu P1396 营救

先从最最最简单的题开始,这题可以利用克鲁斯卡尔最小生成树的思想做,当 \(s\)\(t\) 两点在加边过程中连通了,那么此时的边权就是这条路径上最小的最大边权。

但是可以考虑用bfs的解法做此题。我们不妨二分出答案要求的量,然后跑bfs,如果遇到一条边大于这个二分出来的 \(mid\),就不走这条边,如果可以从 \(s\) 走到 \(t\),则说明这种情况成立,就减小 \(mid\) 继续判断,直到最后求出答案。

代码题解有,也很好写。

Luogu P1948 Telephone Lines S

这题是很巧妙的一题,可以用最短路来解决,只不过需要改一点东西。

先考虑贪心,显然那 \(k\) 个电线杆长度要尽可能大,所以我们只需要付第 \(k+1\) 大的边的钱。

我们可以二分出第 \(k+1\) 大的边所需要的钱,记为 \(mid\)。然后我们把图中小于等于 \(mid\) 的边的边权改为 \(0\),即不需要花免费次数;把大于 \(mid\) 的边的边权改为 \(1\),即需要 \(1\) 次免费次数。最后拉去跑一边最短路,如果终点的最短路径大于 \(k\) 则不成立,\(mid\) 增大,反之成立,\(mid\) 减小,直到最后求出答案。

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,INF=0x3f3f3f3f;
inline int read(){
	int ans=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)){ans=(ans<<3)+(ans<<1)+ch-48;ch=getchar();}
	return ans*f;
}
int n,m,k;
int hd[N],nx[N<<1],to[N<<1],val[N<<1],tot;
void adde(int u,int v,int vol){
	nx[++tot]=hd[u];to[tot]=v;val[tot]=vol;hd[u]=tot;
	nx[++tot]=hd[v];to[tot]=u;val[tot]=vol;hd[v]=tot;
}
int dis[N],vis[N];
bool check(int mid){
	queue<int> q;
	while(!q.empty()) q.pop();
	for(int i=1;i<=n;i++){
		dis[i]=INF;
		vis[i]=0;
	}
	q.push(1),vis[1]=1,dis[1]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		vis[u]=0;
		for(int i=hd[u];i;i=nx[i]){
			int v=to[i],vol=0;
			if(val[i]>mid) vol=1;
			if(dis[v]>dis[u]+vol){
				dis[v]=dis[u]+vol;
				if(!vis[v]) q.push(v);vis[v]=1;
			}
		}
	}
	if(dis[n]>k) return false;
	return true;
}
int main(){
	n=read(),m=read(),k=read();
	for(int i=1;i<=m;i++){
		int x=read(),y=read(),z=read();
		adde(x,y,z);
	}
	int l=0,r=1e6,ans=-1,mid;
	while(l<=r){
		mid=l+r>>1;
		if(check(mid)) r=mid-1,ans=mid;
		else l=mid+1;
	}
	printf("%d",ans);
	return 0;
}

Luogu P1137 旅行计划

这道题其实和最短路没有半毛钱关系,写累了就做做这道水题。

看题,就是求以每个点为起点的最长路,又因为是有向无环图,可以考虑直接拓扑排序解决本题。

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int ans=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)){ans=(ans<<3)+(ans<<1)+ch-48;ch=getchar();}
	return ans*f;
}
const int N=2e5+5;
int n,m,f[N];
int hd[N],nx[N],to[N],in[N],tot;
void adde(int u,int v){
	nx[++tot]=hd[u];to[tot]=v;hd[u]=tot;in[v]++;
}
void topo(){
	queue<int> q;
	while(!q.empty()) q.pop();
	for(int i=1;i<=n;i++)	if(!in[i]){q.push(i);f[i]=1;}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=hd[u];i;i=nx[i]){
			int v=to[i];
			in[v]--;
			f[v]=max(f[v],f[u]+1);
			if(!in[v]) q.push(v);
		}
	}
}	
int main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		int x=read(),y=read();
		adde(x,y);
	}
	topo();
	for(int i=1;i<=n;i++)
		printf("%d\n",f[i]);
	return 0;
}

POJ-2728 Desert King

这题可以考虑用最小生成树解决(最小比值生成树)。

颓一波式子:

众所周知,最小生成树求的是最小的 \(\sum (x_i*w_i)\quad(x_i\in \{0,1\})\) 其中 \(w_i\) 指图中各边边权。

我们不妨设这题要求的比值为 \(ans\)

由题简单可以得到这个式子(\(c_i\)为花费,\(v_i\)为价值)

\[\sum (x_i*c_i) \div \sum(x_i*v_i)>=ans\quad(x_i\in \{0,1\}) \]

移项

\[\sum(x_i*c_i)>=\sum(x_i*v_i*ans) \]

\[\sum(x_i*c_i-x_i*v_i*ans)>=0 \]

化简

\[\sum(x_i*(c_i-v_i*ans))>=0 \]

发现这题就是

\[w_i=c_i-v_i*ans \]

\[\sum(x_i*w_i) \]

因为 \(ans\) 不能确定,所以我们可以二分出 \(ans\) 的取值,不难发现 \(ans\) 越大,生成树的边权和越小,所以如果满足上述不等式,则 \(ans\) 继续增大。

因为这是一张完全图,所以我们应该选用 \(prim\) 算法求最小生成树。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1e3+5,INF=0x3f3f3f3f;
int n,vis[N];
struct QK{
	double x,y,z;
}node[N];
double cost[N][N],dist[N][N],dis[N],mid;
double getdis(QK a,QK b){return sqrt((b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y));}
bool prim(){
	memset(vis,0,sizeof(vis));
	double cnt=0;
	for(int i=1;i<=n;i++) dis[i]=cost[1][i]-mid*dist[1][i];
	vis[1]=1;
	for(int LOL=1,now;LOL<=n;LOL++){
		double minn=INF;
		for(int i=1;i<=n;i++)
			if(!vis[i]&&dis[i]<minn) minn=dis[i],now=i;
		if(minn==INF) break;
		cnt+=minn;
		vis[now]=1;
		for(int i=1;i<=n;i++)
			if(!vis[i]&&dis[i]>cost[now][i]-mid*dist[now][i]) dis[i]=cost[now][i]-mid*dist[now][i];
	}
	if(cnt>=0) return true;
	else return false;
}
int main(){
	while(scanf("%d",&n),n){
		for(int i=1;i<=n;i++) scanf("%lf%lf%lf",&node[i].x,&node[i].y,&node[i].z);
		double mxl=-1,mxc=-1,mil=INF,mic=INF;
		for(int i=1;i<=n;i++){
			for(int j=i+1;j<=n;j++){
				dist[j][i]=dist[i][j]=getdis(node[i],node[j]);
				cost[j][i]=cost[i][j]=abs(node[i].z-node[j].z);
				mxl=max(mxl,dist[i][j]);
				mil=min(mil,dist[i][j]);
				mxc=max(mxc,cost[i][j]);
				mic=min(mic,cost[i][j]);
			}
		}
		double l=mic/mxl,r=mxc/mil;
		while(r-l>1e-4){
			mid=(l+r)/2;
			if(prim()) l=mid;
			else r=mid;
 	 }
 	 printf("%.3lf\n",l);
	}
	return 0;
}
posted @ 2021-01-13 00:19  Quick_Kk  阅读(144)  评论(1编辑  收藏  举报