题目
题解思路
知道是最短路,怎么建图呢?
一开始想到每层来一个超级源点,但是方向不知道怎么确定,用双向边果然WA了(如果是双向边,那一层的都会变成0费连通了 )。
翻了翻 大佬的博客
大佬定义了一种规则,让这个超级源点实现了功能
对于上下层的源点,进行 点 到 源点 单向连通
对于本层源点进行 源点 到 点单向连通
思考一下 如果反过来 也是可行的
属实巧妙啊!
然后就是跑最短路了,这里是10的5次方,朴素的迪杰斯特拉肯定会TLE,刚好没用过堆优化和邻接表,试试水!
堆优化 记得重载运算符 ( 这个运算符是优先队列用的 )
struct bian
{
int z,w;
bool operator < (const bian &other) const
{
return w > other.w;
}
};
AC代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
#define INF 0x3f3f3f3f
int li[200020];
bool vis[200020];
struct bian
{
int z,w;
bool operator < (const bian &other) const
{
return w > other.w;
}
};
vector <bian> head[200020];
int dis[200020] ;
int fb ( int x )
{
if (x < 0 )
return -x;
return x;
}
int main ()
{
ios::sync_with_stdio(false);
int t,sum = 1;
cin>>t;
while(t--)
{
int n,m,c,ceshu = 0;
bian tmm ;
cin>>n>>m>>c;
for ( int i = 1 ;i <= n ; i++)
{
int ti;
cin>>ti;
ceshu = max(ceshu,ti); //取出层数
li [i] = ti ;
}
for (int i = n+1 ; i<= n+ceshu ; i++)
li[i] = i - n; //建立超级源点
for (int i = 1 ;i <= n ; i++ )
{
tmm.z =n+li[i] ;
tmm.w = 0 ;
head[ i ].push_back (tmm); //建立和超级源点的单向边
}
for (int i = 1 ; i<= n ; i++)
{
tmm.w = c;
if ( li[i] > 1 )
{
tmm.z = i ;
head[ n+li[i]-1 ].push_back(tmm); //向和下方的超级源点连接
}
if ( li[i] < ceshu)
{
tmm.z = i ;
head[ n+li[i]+1 ].push_back(tmm); //向和上方的超级源点连接
}
}
for (int i = 1 ;i <= m ; i++ )
{
int a,b,wahs;
cin>>a>>b>>wahs;
tmm.z = b;
tmm.w = wahs;
head[a].push_back(tmm);
tmm.z = a;
head[b].push_back(tmm); //建立原本的额外边
}
/* for (int i = 1 ;i <= ceshu+n ; i++ )
{
printf("以%d为首的边\n",i);
int pint = head[i].size();
for (int k = 0 ;k < pint ; k++ )
printf("%d %d %d\n",i,head[i][k].z,head[i][k].w );
printf("\n");
} */
for (int i = 1 ; i <= n+ceshu ; i++ )
dis[ i ] = INF ; //初始化dis数组
memset(vis,0,sizeof(vis));
dis [ 1 ] = 0; //以1为起点出发
priority_queue < bian > q;
tmm.w = 0;
tmm.z = 1;
q.push(tmm); //将起点放入优先队列
while(!q.empty())
{
tmm = q.top();
q.pop(); //取出和源点最近的点并出队
int go = tmm.z;
if (vis[go])
continue;
vis[go] = 1;
for (int i = 0 ; i <head[go].size() ; i++)
{
tmm = head[go][i]; //对该源点有的边 进行松弛判断
if (dis[tmm.z] > dis[go] + tmm.w )
{
dis[tmm.z] = dis[go] + tmm.w;
bian e;
e.w = dis[tmm.z];
e.z = tmm.z;
q.push(e); //将 已经被松弛的点 和 当前松弛该点到起点的最小距离 入队
}
}
}
cout<<"Case #"<<sum<<": ";
sum++;
if (dis[n] == INF)
cout<<"-1\n";
else
cout<<dis[n]<<"\n";
for (int i = 1 ;i <= n + ceshu ; i++ ) //每次用完初始化
head[i].clear();
}
return 0;
}