题目大意:
题目链接:https://jzoj.net/senior/#main/show/3470
给定一个个点条边的有向图,有个标记点,要求从规定的起点按任意顺序经过所有标记点到达规定的终点,问最短的距离是多少。
思路:
最短路+DFS
首先,对于每一个标记点和点,以它们为起点跑一边SPFA,由于,所以我们就可以得到一个只有和标记点的一个有向图,图的每条边是原图的最短路。
然后就用DFS枚举标记点经过的顺序,时间复杂度为,可以接受。跑完之后就更新答案,最后输出最优答案。
对于的两个点,在跑完以为起点的最短路之后直接输出(或)即可。
代码:
有点丑,请见谅。
#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
#define N 101000
#define Inf 1e9
#define ll_Inf 1e17
#define ll long long
using namespace std;
int n,m,k,s,t,tot,x,y,z,head[N],dis[N],sp[21],a[21][21];
bool vis[N],p[21];
ll ans=ll_Inf;
struct node
{
int next,to,dis;
}e[N];
void add(int from,int to,int dis) //存图(原图)
{
tot++;
e[tot].dis=dis;
e[tot].to=to;
e[tot].next=head[from];
head[from]=tot;
}
void spfa(int start) //以start为原点的SPFA
{
for (int i=0;i<=n;i++)
{
dis[i]=Inf;
vis[i]=0;
}
queue<int> q;
q.push(start);
vis[start]=1;
dis[start]=0;
while (q.size())
{
int u=q.front();
q.pop();
vis[u]=0;
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
if (dis[v]>dis[u]+e[i].dis)
{
dis[v]=dis[u]+e[i].dis;
if (!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
}
void dfs(int x,ll sum,int dep) //DFS枚举顺序
{
if (dep==k) //分配好所有k个点的顺序
{
if (a[x][k+1]==Inf) return;
if (ans>sum+(ll)a[x][k+1]) ans=sum+(ll)a[x][k+1]; //更新答案(第k+1个点是终点)
return;
}
for (int i=1;i<=k;i++) //枚举下一个到达的点
if (x!=i&&!p[i])
{
if (a[x][i]==Inf) continue;
p[i]=1;
dfs(i,sum+(ll)a[x][i],dep+1);
p[i]=0;
}
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d%d%d%d",&n,&m,&k,&s,&t);
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
for (int i=1;i<=k;i++)
scanf("%d",&sp[i]);
spfa(s); //以s点为起点跑SPFA
if (!k) //k为0的特殊情况
{
if (dis[t]!=Inf) printf("%d\n",dis[t]);
else printf("-1");
return 0;
}
for (int i=1;i<=k;i++)
a[0][i]=dis[sp[i]]; //求每个点的图
for (int i=1;i<=k;i++) //以每一个标记点开始跑SPFA
{
spfa(sp[i]);
for (int j=1;j<=k;j++)
a[i][j]=dis[sp[j]];
a[i][k+1]=dis[t]; //一定要记得连终点!!!
}
dfs(0,0,0);
if (ans==ll_Inf)
{
printf("-1");
return 0;
}
cout<<ans;
return 0;
}