***终于写到图论了啊...图论最近争取多多做喏
最近做到有关第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*来解决。感到好疑惑???所以先不要看这部分吧,等我研究好。