题意:
给出一个n点m边的有向图,求s到t的最短路和长度为最短路+1的路的种类数;
n<=1000,m<=10000;
题解:
对于长度仅为最短路+1的路,处理时我们可以放宽一些限制;
只需求最短路和次短路,然后判断一下次短路是否满足情况就好了;
那么,求最短路+次短路的算法就用dij来处理;
令:
dis[x][k]表示x点的最短路次短路长度;
cnt[x][k]表示x点的最短路次短路分别的种类数;
vis[x][k]表示在dij算法中已经固定了的点,不会再次被更新;
k∈{0,1},分别表示最短路和次短路;
然后每次找出所以路中最小的一个,更新所有可到达的点;
分情况讨论累计种类数,就可以得到答案了;
HINT:
1.实际上dij时因为n较小,用heap实现也是可以的,但是代码比较麻烦,所以直接枚举O(n^2)
2.至于用dij而不用spfa是因为dij算法每个结点只会对其它结点进行一次更新;
spfa可能多次入队多次更新造成计数错误,但是记录一些东西也是可以做的;
3.dij的循环要执行2*n次因为有2*n个vis数组要更新;
代码:
#include<vector>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 1100
using namespace std;
vector<int>to[N], val[N];
int dis[N][2], cnt[N][2];
int vis[N][2];
void init(int n)
{
for (int i = 1; i <= n; i++)
to[i].clear(),
val[i].clear();
memset(vis, 0, sizeof(vis));
memset(cnt, 0, sizeof(cnt));
memset(dis, 0x3f, sizeof(dis));
}
void Dij(int n)
{
int s, t, i, j, k, x, y;
bool fl;
scanf("%d%d", &s, &t);
dis[s][0] = 0;
cnt[s][0] = 1;
for (i = 1; i <= 2 * n; i++)
{
k = 0x3f3f3f3f;
for (j = 1; j <= n; j++)
{
if (!vis[j][0] && dis[j][0] < k)
x = j, fl = 0, k = dis[j][0];
else if (!vis[j][1] && dis[j][1] < k)
x = j, fl = 1, k = dis[j][1];
}
if (k == 0x3f3f3f3f)
break;
vis[x][fl] = 1;
for (j = 0; j < to[x].size(); j++)
{
y = to[x][j];
if (dis[y][0]>k + val[x][j])
{
dis[y][1] = dis[y][0], cnt[y][1] = cnt[y][0];
dis[y][0] = k + val[x][j], cnt[y][0] = cnt[x][fl];
}
else if (dis[y][0] == k + val[x][j])
cnt[y][0] += cnt[x][fl];
else if (dis[y][1] > k + val[x][j])
dis[y][1] = k + val[x][j], cnt[y][1] = cnt[x][fl];
else if (dis[y][1] == k + val[x][j])
cnt[y][1] += cnt[x][fl];
}
}
if (dis[t][1] == dis[t][0] + 1)
cnt[t][0] += cnt[t][1];
printf("%d\n", cnt[t][0]);
}
int main()
{
int c, T, n, m, i, j, k, x, y, v;
scanf("%d", &T);
for (c = 1; c <= T; c++)
{
scanf("%d%d", &n, &m);
init(n);
for (i = 1; i <= m; i++)
{
scanf("%d%d%d", &x, &y, &v);
to[x].push_back(y);
val[x].push_back(v);
}
Dij(n);
}
return 0;
}