一、题目
二、解法
0x01 k=0
直接写最短路计数(可以顺手A这个和这个)。
0x02 没有0边
考虑到这道题
k
k
k最大只有
50
50
50,且过程中
k
k
k一定不减,把
k
k
k放进
d
p
dp
dp中,定义
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为到
i
i
i多走了
j
j
j步(相较于最短路),有:
d
p
[
v
]
[
l
+
d
e
l
t
a
]
=
∑
d
p
[
u
]
[
l
]
dp[v][l+delta]=\sum dp[u][l]
dp[v][l+delta]=∑dp[u][l](
u
,
v
u,v
u,v相连)
其中
d
e
l
t
a
delta
delta表示走这条路带来的增量,
d
e
l
t
a
=
d
i
s
[
u
]
+
c
−
d
i
s
[
v
]
delta=dis[u]+c-dis[v]
delta=dis[u]+c−dis[v],易得
d
e
l
t
a
delta
delta非负。
这个
d
p
dp
dp是
O
(
m
k
)
O(mk)
O(mk)的,似乎可以
A
A
A,但发现还存在一个问题,就是我们无法保证
d
p
[
u
]
[
l
]
dp[u][l]
dp[u][l]更新完了后再去更新
d
p
[
v
]
[
l
+
d
e
l
t
a
]
dp[v][l+delta]
dp[v][l+delta],也就是这个算法的正确性被限制在了
d
p
dp
dp的顺序上。
如果
d
e
l
t
a
delta
delta为正,我们可以通过先枚举
l
l
l来解决,而对于
d
e
l
t
a
delta
delta为
0
0
0的情况,我们暂时不考虑
0
0
0边,直接将
d
i
s
dis
dis排序,
d
i
s
dis
dis小的先更新,这样就能解决顺序问题。
(注:这种算法过不了样例,但卡卡常
70
70
70没问题)。
0x03 正解
介绍一种很优秀的做法,考虑到不会带来增量的边在且仅在最短路径上,我们可以通过跑正反向图的方式找出所有不会带来增量的边,然后直接拓扑排序,有环直接输出
−
1
-1
−1,没有环按拓扑序
d
p
dp
dp即可。
为什么能这样做呢?这是因为组成环的只有
0
0
0边(如果有非零边那么跑一遍环就不可能是最短路了)。
m
e
a
n
w
h
i
l
e
meanwhile
meanwhile,没有带来增量的边应该放在最短路上处理,从
1
1
1开始拓扑保证了顺序是符合最短路的。
注意
d
p
dp
dp时先
d
p
dp
dp没有增量的边,然后在
d
p
dp
dp有增量的边就可以了。
0x04 代码
我的思路来源于这位大佬的博客,下面附上我卡过常的代码。
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
const int MAXN = 100005;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int T,n,m,k,p,tot,ans,cnt,a[2*MAXN],b[2*MAXN],w[2*MAXN],tp[MAXN],in[MAXN],f[MAXN],dis[MAXN][2],dp[MAXN][51];
struct edge
{
int v,c,next;
} e[2*MAXN];
struct node
{
int u,c;
bool operator < (node x) const
{
return c>x.c;
}
};
void dijkstra(int cur)
{
priority_queue<node> q;
q.push(node{cur?n:1,0});
dis[cur?n:1][cur]=0;
while(!q.empty())
{
node t=q.top();
q.pop();
for(int i=f[t.u]; i; i=e[i].next)
{
int v=e[i].v,c=e[i].c;
if(dis[v][cur]>t.c+c)
{
dis[v][cur]=t.c+c;
q.push(node{v,dis[v][cur]});
}
}
}
}
bool topol()
{
if(in[1]) return 0;
queue<int> q;
q.push(1);
while(!q.empty())
{
int u=q.front();
q.pop();
tp[++cnt]=u;
for(int i=f[u]; i; i=e[i].next)
{
int v=e[i].v;
in[v]--;
if(in[v]==0)
q.push(v);
}
}
for(int i=1; i<=n; i++)
if(in[i])
return 0;
return 1;
}
void makeSet()
{
tot=0;
for(int i=1; i<=n; i++)
f[i]=0;
}
int main()
{
T=read();
while(T--)
{
n=read();
m=read();
k=read();
p=read();
tot=cnt=ans=0;
memset(dis,0x3f,sizeof dis);
for(int i=1; i<=n; i++)
{
f[i]=in[i]=0;
}
for(int i=1; i<=n; i++)
for(int j=0; j<=k; j++)
dp[i][j]=0;
for(int i=1; i<=m; i++)
{
int u=read(),v=read(),c=read();
a[i]=u;
b[i]=v;
w[i]=c;
e[++tot]=edge{v,c,f[u]},f[u]=tot;
}
dijkstra(0);
makeSet();
for(int i=1; i<=m; i++)
e[++tot]=edge{a[i],w[i],f[b[i]]},f[b[i]]=tot;
dijkstra(1);
makeSet();
for(int i=1; i<=m; i++)
{
int u=a[i],v=b[i],c=w[i];
if(dis[u][0]+dis[v][1]+c<=dis[n][0]+k && dis[u][0]+c==dis[v][0])
{
e[++tot]=edge{v,0,f[u]},f[u]=tot;
in[v]++;
}
}
if(!topol())
{
puts("-1");
continue ;
}
dp[1][0]=1;
for(int l=0; l<=k; l++)
{
for(int i=1; i<=cnt; i++)
{
int u=tp[i];
for(int j=f[u]; j; j=e[j].next)
{
int v=e[j].v;
dp[v][l]+=dp[u][l];
dp[v][l]%=p;
}
}
for(int i=1; i<=m; i++)
{
int u=a[i],v=b[i],c=w[i],delta=dis[u][0]+c-dis[v][0];
if(delta+l<=k && delta)
{
dp[v][l+delta]+=dp[u][l];
dp[v][l+delta]%=p;
}
}
}
for(int i=0; i<=k; i++)
ans=(ans+dp[n][i])%p;
printf("%d\n",ans);
}
}