题目地址
分析
- 设
dis1[u]
表示
1→u
的最短路长度,
disn[u]
表示
u→n
的最短路长度
【30pts】拓扑排序 + DP
- 这里针对的是
K=0
的情况,问题转化为求图中的最短路径数
- 我们先将最短路图建出来(对于每条边
u→v
,满足
dis1[u]+lenu→v=dis1[v]
),因为没有
0
边所以就是个有向无环图,直接按照拓扑序计算DP即可
【100pts】记忆化搜索 + DP + Tarjan
- 首先应该能很容易想到这还是个
DP
- 然后考虑怎样定义状态:
- 考场上我想得最为简单暴力,设
g[u][k]
表示到达点
u
,长度为k的路径数,显然
g[u][k]=∑g[v][k−lenu→v]
,但空间时间都要爆炸,于是粗略算了空间后听天由命……
- 但其实能注意到
K
的范围相当小,于是我们设f[u][k]表示到达点
u
,长度为dis1[u]+k的路径数,则转移为
f[u][k]=∑f[v][k−(dis1[u]+lenu→v−dis1[v])]
- 考虑到边权可能为
0
,转移的顺序会影响最后的结果,因此我们用记忆化搜索代替直接DP
- 那么现在只剩下一个问题:如何判无解
- 显然无解的情况就是在满足条件的路径上出现
0
环
- 如何判断一个点u是否在满足条件的路径上?显然我们可以知道这个点满足
dis1[u]+disn[u]≤dis1[n]+K
- 我们把所有长度为
0
的边建成一个新图,然后用Tarjan找出所有点数大于
1
的强连通分量(也就是0环),逐个检查上面的点即可
- 时间复杂度
O(MK)
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
inline int get()
{
char ch; int res = 0; bool Flag = false;
while (!isdigit(ch = getchar()));
(ch == '-' ? Flag = true : res = ch ^ 48);
while (isdigit(ch = getchar()))
res = res * 10 + ch - 48;
return Flag ? -res : res;
}
const int Maxn = 0x3f3f3f3f;
const int N = 1e5 + 5, M = N << 1;
int D1[N], Dn[N], h[M], f[N][55], a[N];
int Tm, n, m, K, P, Ans, top, tis;
int dfn[N], low[N], stk[N];
bool vis[N], inv[N], Flag;
struct Edge
{
int to, cst; Edge *nxt;
};
Edge p[M], *T = p, *lst[N];
Edge q[M], *Q = q, *rst[N];
inline void LinkEdge(int x, int y, int z)
{
(++T)->nxt = lst[x]; lst[x] = T;
T->to = y; T->cst = z;
}
inline void RinkEdge(int x, int y, int z)
{
(++Q)->nxt = rst[x]; rst[x] = Q;
Q->to = y; Q->cst = z;
}
inline void Spfa(int src, int *dis)
{
for (int i = 1; i <= n; ++i) dis[i] = Maxn;
int t = 0, w = 1, x, y; dis[h[1] = src] = 0;
while (t != w)
{
++t; if (t == M) t = 0;
vis[x = h[t]] = false;
for (Edge *e = src == 1 ? lst[x] : rst[x]; e; e = e->nxt)
if (dis[y = e->to] > dis[x] + e->cst)
{
dis[y] = dis[x] + e->cst;
if (!vis[y])
{
++w; if (w == M) w = 0;
vis[h[w] = y] = true;
}
}
}
}
inline void Add(int &x, int y)
{
x += y; if (x >= P) x -= P;
}
inline int Dfs(int x, int d)
{
if (f[x][d] != -1) return f[x][d];
if (x == n && !d) return f[x][d] = 1;
f[x][d] = 0;
for (Edge *e = lst[x]; e; e = e->nxt)
{
int tmp = d - e->cst;
if (tmp >= 0 && tmp <= K)
Add(f[x][d], Dfs(e->to, tmp));
}
return f[x][d];
}
inline void CkMin(int &x, int y)
{
if (x > y) x = y;
}
inline void Tarjan(int x)
{
if (Flag) return ;
dfn[x] = low[x] = ++tis; int y;
inv[x] = true; stk[++top] = x;
for (Edge *e = rst[x]; e; e = e->nxt)
{
if (e->cst) continue;
if (!dfn[y = e->to])
Tarjan(y), CkMin(low[x], low[y]);
else if (inv[y])
CkMin(low[x], dfn[y]);
}
if (dfn[x] == low[x])
{
inv[x] = false; bool num = false;
while (y = stk[top--], y != x)
inv[y] = false, num = true;
if (D1[x] + Dn[x] <= D1[n] + K && num) Flag = true;
}
}
int main()
{
Tm = get();
while (Tm--)
{
memset(lst, 0, sizeof(lst)); T = p;
memset(rst, 0, sizeof(rst)); Q = q;
int x, y, z;
n = get(); m = get(); K = get(); P = get();
while (m--)
{
x = get(); y = get(); z = get();
LinkEdge(x, y, z);
RinkEdge(y, x, z);
}
Spfa(1, D1); Spfa(n, Dn);
memset(dfn, 0, sizeof(dfn));
memset(inv, false, sizeof(inv)); Flag = false;
for (int i = 1; i <= n; ++i)
if (!dfn[i]) Tarjan(i);
if (Flag)
{
puts("-1");
continue;
}
for (int i = 1; i <= n; ++i)
for (Edge *e = lst[i]; e; e = e->nxt)
e->cst += D1[i] - D1[e->to];
for (int k = 0; k <= K; ++k)
for (int i = 1; i <= n; ++i)
f[i][k] = -1;
Ans = 0;
for (int k = 0; k <= K; ++k)
Add(Ans, Dfs(1, k));
printf("%d\n", Ans);
}
return 0;
}