题意:
给出一个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; }