第k短路 两种类型

***终于写到图论了啊...图论最近争取多多做喏

最近做到有关第k短路的题目,感觉很有价值哦,就整理整理叭~(最近刚看完《挪威的森林》,说话不知道为什么超级有口音,喜欢加很多“哦”“哟”“喏”之类的语气词...要改改哦)

1.求所有路径中的第k短路


图 (graph)

时间限制: 3.000 Sec  内存限制: 128 MB
提交状态

题目描述

Peaunt.Tang(std) 给了 Vxlimo 一个 n 个点,m 条边的无向连通加权图,他想让 Vxlimo 求出这个图中第 k 短的最短路长度。

然而 Vxlimo 实在是太菜了,只能向你求助。

输入

第一行三个正整数 n,m,k,表示这个图的点数,边数和k。

接下来 m 行,每行三个正整数 x,y,v,表示 x 和 y 之间有一条权值为 v 的无向边。

输出

一行一个整数 ans,表示这个图中第 k 短的最短路(从 i 到 j 和从 j 到 i 的路径算作同一条,从 i 到 i 的路径不算)。

样例输入 Copy
【样例1】
6 10 5
2 5 1
5 3 9
6 2 2
1 3 1
5 1 8
6 5 10
1 6 5
6 4 6
3 6 2
3 4 5
【样例2】
7 15 18
2 6 3
5 7 4
6 5 4
3 6 9
6 7 7
1 6 4
7 1 6
7 2 1
4 3 2
3 2 8
5 3 6
2 5 5
3 7 9
4 1 8
2 1 1
样例输出 Copy
【样例1】
3
【样例2】
9
提示

样例1解释:
前 5 短的最短路:1 ,1,2,2,3。


思路:这题求所有路径中的最短路,没有规定起点与终点,那可以想到暴力来做的话,就是把所有点之间的路用floyd都求出来,然后排序一下~第k个输出即可啦~

But,注意这题点的数据范围。如果要求f[i][j](从i到j的距离),那么二维数组肯定是存不下这么多点的。这时就要想到离散化(或者是排序),来进行优化。我们看到k是有数据范围的,k一定小于400,这是比较小的。也就是说我们不用把所有边都求出来,只要求前k短的直线边即可,因为所有边中的前k条也一定是由前k条边组成的(说的有点绕,可以看代码)。这就可以筛选很多点了。剩余的点由于数量少了,我们不要直接存标号,我们将它们离散化即可。

(P.S.说到这筛选边不要全选,我想到我之前写过一道最小生成树的题目,也是点很多,不能全选边,而且也是尽量选小点的边。有兴趣的朋友可以取看看哦)

提要:感觉这题有几个点比较精彩,写下来:

1.边的筛选 与 离散化

2.floyd求点距

3.感觉思路有点像最小生成树(莫名其妙),都是把可能的都求出来,排序再筛选。

看看代码叭~ 

#include<iostream>
#include<unordered_map>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
int n,m,k;
struct EDGE{
    int u,v;
    ll l;
}edge[200010];
int f[888][888];
unordered_map<int,int>map;
int id;
bool sorrt(struct EDGE i,struct EDGE j){
    return i.l<j.l;
}
int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++){
        cin>>edge[i].u>>edge[i].v>>edge[i].l;
    }
    sort(edge+1,edge+m+1,sorrt);
    memset(f,0x3f,sizeof f);
    //离散化
    for(int i=1;i<=k;i++){//注意这里是k,不是n!
        int u=edge[i].u,v=edge[i].v,l=edge[i].l;
        if(map.count(u)==false)map[u]=id++;
        if(map.count(v)==false)map[v]=id++;
        f[map[u]][map[v]]=f[map[v]][map[u]]=min(f[map[u]][map[v]],l);
    }
     
    n=id;
    //floyd求两点间距离
    for(int k=0;k<n;k++){
        for(int i=0;i<n;i++){
            for(int j=1;j<n;j++){
                f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
            }
        }
    }
    vector<ll>ans;
    for(int i=0;i<n;i++){
        for(int j=i+1;j<n;j++){
            ans.push_back(f[i][j]);
        }
    }
    //排序
    sort(ans.begin(),ans.end());
    cout<<ans[k-1];
    return 0;
}

2.求定起点与定终点的第k短路

提要:既然起点和终点都已经确定了,那么就不能用floyd了,要用一个优化的Dijkstra。

下面我先贴一下堆优化版Dijkstra的模板(毕竟今天才会,正好复习一下)

typedef pair<int,int> PII;
int d[N];//N为点数
struct EDGE{
	int u,v,l;
	EDGE* ne;
}edge[M];
struct NODE{
	EDGE* fir;
}node[N];
int n,m,tot;
int s,e,ds[N],pre[N];
void build(int u,int v,int l){
	edge[tot].u=u;
	edge[tot].v=v;
	edge[tot].l=l;
	edge[tot].ne=node[u].fir;
	node[u].fir=&edge[tot];
	tot++;
}
void Dijkstra(int s){
	memset(d,0x3f,sizeof d);
	priority_queue<PII>q;//优先队列是大顶堆,存储距离的负值,这样较方便;
	PII t;
	d[s]=0;
	q.push({0,s});//前一个int是点,后一个是距离;
	while(q.size()){
		t=q.top();q.pop();
		int u=t.second;
		//注意要判断一下是否搜过!!!
		if(ds[u])continue;
		ds[u]=1;
		EDGE* v=node[u].fir;
		while(v!=NULL){
			if(d[v]>d[u]+t.second){
				pre[v]=u;//记录前驱点;
				d[v]=d[u]+t.second;
				q.push({-d[v],v});
			}
			v=v->ne;
		}
	}
}
void dfs_path(int u){//递归输出路径
	if(u==s){
		cout<<u<<'\n';
		return ;
	}
	dfs(pre[u]);
	cout<<u<<'\n';
}
int main(){
	cin>>n>>m;
	int u,v,l;
	while(m--){
		cin>>u>>v>>l;
		build(u,v,l);
	}
	//...略~也~
}

下面就是用A*算法解决这个问题(半成品):

#include<iostream>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N=1e5+7;
const int M=1e5+7;
int n,m,k,a,b;
struct EDGE{
	int u,v,l;
	EDGE* ne;
}edge[M];
struct NODE{
	EDGE* fir;
	EDGE* difir;
}node[N];
int tot;
void build(int u,int v,int l){
	edge[tot].u=u;
	edge[tot].v=v;
	edge[tot].l=l;
	edge[tot].ne=node[u].fir;
	node[u].fir=&edge[tot];
	tot++;
}
void dibuild(int u,int v,int l){
	edge[tot].u=u;
	edge[tot].v=v;
	edge[tot].l=l;
	edge[tot].ne=node[u].difir;
	node[u].difir=&edge[tot];
	tot++;
}
int ds[N],d[N];//d为这么多点到终点e的距离
void dijkstra(int s){
	memset(d,0x3f,sizeof d);
	priority_queue<PII>q;
	d[s]=0;
	q.push({0,s});
	PII t;
	while(q.size()){
		t=q.top();q.pop();
		int u=t.second;
		if(ds[u])continue;
		ds[u]=1;
		EDGE* k=node[u].difir;
		while(k!=NULL){
			if(d[k->v]>d[u]+k->l){
				d[k->v]=d[u]+k->l;
				q.push({-d[k->v],k->v});
			}
			k=k->ne;
		}
	}
}
struct COM{
	int s,v,d;
	bool operator<(const COM &x)const{
		return s>x.s;
	}
};
int cnt[100010],pre[1000010];
int A(int s,int e){
	priority_queue<COM> que;
	COM a={d[s],s,0};
	que.push(a);
	while(que.size()){
		COM t=que.top();que.pop();
		int u=t.v,dd=t.d;
		cnt[u]++;
		if(cnt[e]==k) return dd;
		EDGE* i=node[u].fir;
		while(i!=NULL){
			if(cnt[i->v]<k){
				pre[i->v]=u;
				COM a={dd+i->l+d[i->v],i->v,dd+i->l};
				que.push(a);
			}
			i=i->ne;
		}
	}	
	return -1;
}

int main()
{
	cin>>n>>m>>k>>a>>b;
	int u,v,l;
	while(m--){
		cin>>u>>v>>l;
		build(u,v,l);
		dibuild(v,u,l);
	}
	dijkstra(b);
	A(a,b);
	printf("%d",A(a,b));
	return 0;
}

但我现在还没研究到这么打印这些点...并且刚刚看洛谷题解好多大佬都说这题不要用A*来解决。感到好疑惑???所以先不要看这部分吧,等我研究好。

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值