带限制最短路。(4月9日更新:这道题的依赖关系很像拓扑排序,注意体会)
一个有向图,给定起点终点,求结点依赖关系下的最短路。所谓结点依赖,是指每个结点i
都依赖于零个或多个其他结点,结点i
可访问当且仅当它依赖的这些结点都已经访问过。(可以确定的是,起点肯定不依赖于任何点,而且每个点都不可能依赖于自身)
还有,这道题边权的实际意义是时间。这一点和其他最短路的题都不一样!所以d[]
数组代表的就是两点之间最短路花费的时间(不仅包括走每条边花的时间,还包括等的时间)。
这道题(只能? )用dijkstra
完成,dijkstra
最短路算法可以保证每次大循环找到的u
点不重复(因为有vis[]
数组控制着),每个点最多只当一次中介点。然后,某个点成为中介点的必要条件是它依赖的那些点都已经访问过(这里用数组cnt[]
记录每个点依赖的那些结点还差几个没访问)。当找到一个合法的中介点以后,要更新所有的依赖这个中介点的那些结点(这里是依赖当前点的点,不是当前点依赖的点)的信息,首先这些点各自的cnt[]
可以减一了,然后这些点的d[]
如果小于d[u]
,则置为d[u]
(因为这些点依赖于中介点u
,中介点被访问完才有可能访问这些点,所以这些点的d[]
至少是d[u]
)。
上述说的有点绕,其实A
依赖B
的意思就是B
城市里面装有A
城市的屏障发生器。根据上述思想,我们需要统计A
依赖的点的个数,以及B
指向A
的这个关系(也就是说通过B
能找到A
)。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
using namespace std;
const int INF = 1e9;
const int MAXN = 3001;
int N, M, T;
struct Edge
{
int n, w;
};
vector<Edge> ve;
vector<int> v[MAXN];
vector<int> to[MAXN];
int cnt[MAXN];
int d[MAXN];
bool vis[MAXN];
void init()
{
ve.clear();
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= N; i++)
{
v[i].clear();
to[i].clear();
}
}
void dijkstra(int s)
{
memset(vis, 0, sizeof vis);
fill(d + 1, d + N + 1, INF);
d[s] = 0;
for (int i = 0; i < N; i++)
{
int u = -1, mind = INF;
for (int j = 1; j <= N; j++)
{
if (!cnt[j])
{
if (!vis[j] && d[j] < mind)
{
mind = d[j];
u = j;
}
}
}
vis[u] = true;
for (int j = 0; j < to[u].size(); j++) 对dijkstra而言,u只当这么一次中介点
{
d[to[u][j]] = max(d[to[u][j]], d[u]);
cnt[to[u][j]]--;
}
for (int j = 0; j < v[u].size(); j++)
{
int n = ve[v[u][j]].n;
int w = ve[v[u][j]].w;
if (!vis[n] && d[u] + w < d[n])
{
d[n] = d[u] + w;
}
}
}
}
int main()
{
int a, b, c;
scanf("%d", &T);
for (; T--;)
{
scanf("%d%d", &N, &M);
init();
for (int i = 0; i < M; i++)
{
scanf("%d%d%d", &a, &b, &c);
ve.push_back(Edge{ b,c });
v[a].push_back(i);
}
for (int i = 1; i <= N; i++)
{
scanf("%d", &a);
for (; a--;)
{
scanf("%d", &b);
cnt[i]++; // 当前城市i被多少个城市保护着
to[b].push_back(i); // 城市b保护当前城市i
}
}
dijkstra(1);
printf("%d\n", d[N]);
}
return 0;
}