最短路+记忆化搜索。
一个无向图,问你从起点到终点有多少条这样的路,该路径包含的边a->b
都满足至少存在一条b
到终点的路比所有a
到终点的路都要短。这道题中path
指边,route
指路径。
这道题比较良心,说了权值都为正。首先这个条件等价于b
到终点的最短路小于a
到终点的最短路。那么先求所有点到终点的最短路,因为是无向图所以不用反向建边了,直接以终点为源点求最短路。
接下来从起点开始dfs
,用cnt[]
表示从当前点到终点的路径条数,所以当前点的路径条数等于它的所有合法邻接点的路径条数之和。这里就出现了两个动态规划的特征,最优子结构(原问题的最优解可通过子问题的最优解构造出来)和重叠子问题,所谓重叠,指的是多条支路可能汇聚于中间一点,而对于每一条路径而言,一定是简单路!(想想该路径的定义,沿着该路径走,每个点到终点的最短路的值一定越来越小)
既然意识到了重叠子问题,那么就不用重复求相同的子问题了。所以这就是记忆化搜索。另外,如果不想(会超时的)记忆化搜索,应该在每次计算当前点cnt[]
之前先初始化为0
,因为这个点可能已经计算过了(想象多条支路汇聚的点)。
另外,这道题说的路径完全可以不是最短路(如果是的话直接求最短路条数不就完了吗。。),比如下图:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#include <queue>
#include <utility>
using namespace std;
const int INF = 0x3F3F3F3F;
const int MAXN = 1001;
int N, M;
vector<pair<int, int>> v[MAXN];
int d[MAXN];
bool inq[MAXN];
int cnt[MAXN]; // 某点到终点(2)有多少条路
void init()
{
for (int i = 1; i <= N; i++) v[i].clear();
memset(cnt, 0, sizeof cnt);
//cnt[2] = 1; // 终点到终点有1条路
}
void spfa(int s)
{
memset(d, 0x3F, sizeof d);
memset(inq, 0, sizeof inq);
d[s] = 0;
queue<int> q;
q.push(s);
inq[s] = true;
for (; !q.empty();)
{
int t = q.front();
q.pop();
inq[t] = false;
for (int i = 0; i < v[t].size(); i++)
{
int n = v[t][i].first;
int w = v[t][i].second;
if (d[t] + w < d[n])
{
d[n] = d[t] + w;
if (!inq[n])
{
q.push(n);
inq[n] = true;
}
}
}
}
}
int dfs(int s)
{
if (s == 2) return 1;
if (cnt[s] > 0) return cnt[s]; // 这就是记忆化搜索,若把这行改成 cnt[s]=0,会超时的
for (int i = 0; i < v[s].size(); i++)
{
int n = v[s][i].first;
if (d[n] < d[s])
cnt[s] += dfs(n);
}
return cnt[s];
}
int main()
{
int a, b, c;
for (; ~scanf("%d", &N);)
{
if (!N) break;
scanf("%d", &M);
init();
for (int i = 0; i < M; i++)
{
scanf("%d%d%d", &a, &b, &c);
v[a].push_back(make_pair(b, c));
v[b].push_back(make_pair(a, c));
}
spfa(2);
printf("%d\n", dfs(1));
}
return 0;
}