参考资料:
_风休住
大佬的题解策策同学特别喜欢逛公园。公园可以看成一张 N N N 个点 M M M 条边构成的有向图,且没有 自环和重边。其中 1 1 1 号点是公园的入口, N N N 号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。
策策每天都会去逛公园,他总是从 1 1 1 号点进去,从 N N N 号点出来。
策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果 1 1 1 号点 到 N N N 号点的最短路长为 d d d,那么策策只会喜欢长度不超过 d + K d + K d+K 的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?
为避免输出过大,答案对 P P P 取模。
如果有无穷多条合法的路线,请输出 − 1 -1 −1。
第一行包含一个整数 T T T, 代表数据组数。
接下来 T T T 组数据,对于每组数据: 第一行包含四个整数 N , M , K , P N,M,K,P N,M,K,P,每两个整数之间用一个空格隔开。
接下来 M M M 行,每行三个整数 a i , b i , c i a_i,b_i,c_i ai,bi,ci,代表编号为 a i , b i a_i,b_i ai,bi 的点之间有一条权值为 c i c_i ci 的有向边,每两个整数之间用一个空格隔开。
输出文件包含 T T T 行,每行一个整数代表答案。
2
5 7 2 10
1 2 1
2 4 0
4 5 2
2 3 2
3 4 1
3 5 2
1 5 3
2 2 0 10
1 2 0
2 1 0
3
-1
【样例解释1】
对于第一组数据,最短路为 3 3 3。 1 → 5 , 1 → 2 → 4 → 5 , 1 → 2 → 3 → 5 1\to 5, 1\to 2\to 4\to 5, 1\to 2\to 3\to 5 1→5,1→2→4→5,1→2→3→5 为 3 3 3 条合法路径。
【测试数据与约定】
N ≤ 1 0 5 N \leq 10^5 N≤105, M ≤ 2 × 1 0 5 M \leq 2\times10^5 M≤2×105, K ≤ 50 K \leq 50 K≤50。
最短路+dp
本题需要建正、反两张图,正图用于求最短路,反图用于记忆化搜索和dp。
由于本题目中约定每条边的权值 ≥ 0 \geq 0 ≥0,所以图中不存在负环,可以在正图上运行迪杰斯特拉算法求出起点到所有结点的最短距离,保存在数组d[]
中,其中,d[i]
表示起点到结点i
的最短距离。
然后就到了本题的精髓之处:动态规划。定义状态dp[i][k]
表示:从起点到结点i
的长度恰好为d[i]+k
的路径的条数。显然,问题所求的答案为:dp[n][0] + dp[n][1] + ... + dp[n][K]
。然后考虑状态之间的转移。假设在正图中有一条有向边 ( v , u ) (v,u) (v,u),其权值为 w w w,则我们可以通过状态dp[v][x]
转移到状态dp[u][k]
。对于结点u, v
有如下关系:
d [ v ] + x + w = d [ u ] + k d[v]+x+w=d[u]+k d[v]+x+w=d[u]+k
于是有:
x = d [ u ] − d [ v ] + k − w x=d[u]-d[v]+k-w x=d[u]−d[v]+k−w
由此,我们得到状态转移方程:
d p [ u ] [ k ] = ∑ ( v , u ) ∈ G 1 d p [ v ] [ d [ v ] − d [ u ] + k − w ] dp[u][k]=\sum_{(v,u)\in G_1}dp[v][d[v]-d[u]+k-w] dp[u][k]=(v,u)∈G1∑dp[v][d[v]−d[u]+k−w]
在反图上进行记忆化搜索即可实现动态规划。
然后我们考虑输出为-1
的情况:分析题目可知,只有当图中存在零环,且该零环中某一结点出现在某条合法路径上时,最终输出才为-1
。我们可以在记忆化搜索的过程中找出零环。假设当前要求dp[i][k]
,我们沿着反图进行记忆化搜索,在得到最终的dp[i][k]
之前,我们如果回到了dp[i][k]
,说明结点i
在一个零环上。
我们还需要考虑动态规划的边界条件。首先,对于任意一个点i
,当 k < 0 k<0 k<0时,dp[i][k]
一定为0
。再者,我们在进行真正的记忆化搜索前,需要先记忆化搜索dp[1][0],如果在搜索的过程中发现零环,则直接输出-1
即可,否则令dp[1][0]
为1
。
#include
#define maxn 200005
using namespace std;
int T;
int n, m, K, p;
int cnt1 = 0, cnt2 = 0;
int head1[maxn], head2[maxn];
int d[maxn];
bool vis[maxn];
int dp[maxn][55];
bool ins[maxn][55];
bool infty;
int ans;
struct EDGE{
int to;
int v;
int next;
}edge1[maxn], edge2[maxn];
void addEdge1(int fr, int to, int v){
cnt1++;
edge1[cnt1].to = to;
edge1[cnt1].v = v;
edge1[cnt1].next = head1[fr];
head1[fr] = cnt1;
}
void addEdge2(int fr, int to, int v){
cnt2++;
edge2[cnt2].to = to;
edge2[cnt2].v = v;
edge2[cnt2].next = head2[fr];
head2[fr] = cnt2;
}
struct cmp{
bool operator()(pair<int, int>p1, pair<int, int>p2){
return p1.second>p2.second;
}
};
void dij(int i){
memset(d, 37, sizeof(d));
priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> q;
q.push(make_pair(i, 0));
d[i] = 0;
while(!q.empty()){
pair<int, int> p = q.top();
q.pop();
int pos = p.first;
if(vis[pos]) continue;
vis[pos] = 1;
for(int i=head1[pos];i!=0;i=edge1[i].next){
int to = edge1[i].to, v = edge1[i].v;
if(vis[to]) continue;
if(d[to]>d[pos]+v){
d[to] = d[pos]+v;
q.push(make_pair(to, d[to]));
}
}
}
}
void dfs(int u, int k){
if(infty) return;
if(k<0) return;
if(ins[u][k]){
infty = true;
return;
}
if(dp[u][k]!=0) return;
ins[u][k] = true;
int res = 0;
for(int i=head2[u];i!=0;i=edge2[i].next){
int to = edge2[i].to, v = edge2[i].v;
int x = d[u]-d[to]+k-v;
dfs(to, x);
if(x>=0) res += dp[to][x];
res %= p;
}
dp[u][k] = res;
ins[u][k] = false;
}
void init(){
cnt1 = cnt2 = 0;
memset(head1, 0, sizeof(head1));
memset(head2, 0, sizeof(head2));
memset(edge1, 0, sizeof(edge1));
memset(edge2, 0, sizeof(edge2));
memset(d, 0, sizeof(d));
memset(vis, 0, sizeof(vis));
memset(ins, 0, sizeof(ins));
memset(dp, 0, sizeof(dp));
infty = false;
ans = 0;
}
int main(){
cin>>T;
while(T--){
init();
cin>>n>>m>>K>>p;
for(int i=0;i<m;i++){
int a, b, c;
cin>>a>>b>>c;
addEdge1(a, b, c);
addEdge2(b, a, c);
}
dij(1);
dfs(1, 0);
dp[1][0] = 1;
if(infty){
cout<<-1<<'\n';
continue;
}
for(int i=0;i<=K;i++){
dfs(n, i);
if(infty) break;
ans += dp[n][i];
ans %= p;
}
if(infty) cout<<-1<<'\n';
else cout<<ans<<'\n';
}
return 0;
}