题目描述
策策同学特别喜欢逛公园。公园可以看成一张\(N\)个点\(M\)条边构成的有向图,且没有自环和重边。其中\(1\)号点是公园的入口,\(N\)号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。
策策每天都会去逛公园,他总是从\(1\)号点进去,从\(N\)号点出来。
策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果\(1\)号点 到\(N\)号点的最短路长为\(d\),那么策策只会喜欢长度不超过\(d+K\)的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?
为避免输出过大,答案对\(P\)取模。
如果有无穷多条合法的路线,请输出\(-1\)。
输入格式:
第一行包含一个整数 \(T\) , 代表数据组数。
接下来 \(T\) 组数据,对于每组数据: 第一行包含四个整数 \(N,M,K,P\), 每两个整数之间用一个空格隔开。
接下来 \(M\) 行,每行三个整数\(ai,bi,ci\),代表编号为\(ai,bi\)的点之间有一条权值为 \(ci\) 的有向边,每两个整数之间用一个空格隔开。
输出格式:
输出文件包含 \(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
说明
样例解释
对于第一组数据,最短路为 \(3\)。 \(1\) – \(5\), \(1\) – \(2\) – \(4\) – \(5\), \(1\) – \(2\) – \(3\) – \(5\) 为 \(3\) 条合法路径。
测试数据与约定
对于\(100\)%的数据,\(1≤P≤10^9,1≤ai,bi≤N,0≤ci≤10001≤P≤109,1≤ai,bi≤N,0≤ci≤1000\)
数据保证:至少存在一条合法的路线。
solution
先跑一遍\(dijsktra\)预处理出1到所有点的最短路(在原图上)
\(dist[i]\)表示1到i的最短路(不能到达为INF)
然后我们有两种方法:
- 记忆化搜索
- 拓扑排序+\(dp\)(暂时不讨论)
我们建反图,从n开始进行记忆化搜索
我们设\(dp[i][j]\)表示从点\(1\)到点\(i\)距离为\(dist[i]+j\)的路径条数
对于一条反边\((u,v,cost)\)
都有\(dp[u][d]=\sum dp[v][dist[u]+d-cost-dist[v]]\) ①
那么如何判断有没有\(0\)环呢?
如果一个\(dp\)值被经过两次,而且没有被确定,那必定是有\(0\)环了。
那么如何计算\(0\)环对答案产生的贡献呢?
假设\(u\)点在\(0\)环上,现在要计算点\(1\)到点\(u\)距离为\(dist[u]+k\)的答案(dp[u][k])
若\(dist[u]+d-cost-dist[v]≥0\) 则不可能从这条边到达点1且满足条件,不合法② 不从它转移
否则如果\(dp[v][dist[u]+d-cost-dist[v]]\)能走到终点②,没有被确定而且是经过了第二次
那么这整个询问答案为\(-1\),否则继续转移 ③
证明①:\(dp[u][d]\)表示从点\(1\)到点\(u\)距离为\(dist[u]+d\)的路径条数
对于任意满足反边\((u,v,cost)\)的\(v\),从点\(1\)到点\(u\)距离为\(dist[u]+d\)的路径条数=$\sum $点\(1\)到点\(v\)距离为\(dist[u]+d-cost\)的路径条数(走了这条边,剩下距离减去这条边长)
令:\(k=dist[u]+d-cost\)
点\(1\)到点\(u\)距离为\(dist[u]+d\)的路径条数=$\sum $点\(1\)到点\(v\)距离为\(k\)的路径条数
即\(dp[u][d]\)=$\sum $点\(1\)到点\(v\)距离为\(k\)的路径条数
令:点\(1\)到点\(v\)距离为\(k\)的路径条数=\(dp[v][nd]\)
由\(dp\)的定义,\(dp[v][nd]\)=点\(1\)到点\(v\)距离为\(dist[v]+nd\)的路径条数
得:\(dist[v]+nd=k\)
\(nd=k-dist[v]\)
\(nd=dist[u]+d-cost-dist[v]\)
∴\(dp[u][d]=\sum dp[v][nd]=\sum dp[v][dist[u]+d-cost-dist[v]]\)
证完。
证明②:
\(dist[u]+d-cost-dist[v]<0\) 则不能从它走到终点(显然,它走过这条边走最短的距离也到不了终点)
\(dist[u]+d-cost-dist[v]≥0\) 则能从它走到终点(显然,它走过这条边至少走最短的距离能到终点)
证完。
证明③:
判断1:若\(dist[u]+d-cost-dist[v]<0\) 不能从它走到终点,它必定不是答案,有0环的话也没有贡献
判断2:\(dp[v][dist[u]+d-cost-dist[v]]\)没有被确定而且是经过了第二次,而且它必定有方案能在≤规定距离走到终点,但它包含在0环内(这就是0环能遇到的充要条件)那么这整个询问答案为\(-1\)
证完。
注意:请勿直接给dp赋初值,那么判0环会漏掉初值点
//原图最短路,反图记忆化搜索
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>,greater<pii> > que;
int T,n,m,k,p,u,v,cost,ans;
int eHead[110000],EHead[110000],dis[110000],dp[110000][61];
bool zeroring,vis[110000],flag[110000][55];
struct E{
int v,cost,Next;
} edge[210000],Edge[210000];
void init(){
//邻接表
memset(eHead,0,sizeof(eHead));
memset(EHead,0,sizeof(EHead));
//最短路
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(vis));
//dp
memset(dp,-1,sizeof(dp));
}
void addedge(int i,int u,int v,int cost){//加边,原图和反图
edge[i].v=v; edge[i].cost=cost;
edge[i].Next=eHead[u]; eHead[u]=i;
Edge[i].v=u; Edge[i].cost=cost;
Edge[i].Next=EHead[v]; EHead[v]=i;
}
void dijsktra(int s){//最短路
dis[s]=0;
que.push(pii(0,s));
int u,v,cost;
while (!que.empty()){
u=que.top().second;
que.pop();
if (vis[u]) continue;
vis[u]=true;
for (int e=eHead[u];e;e=edge[e].Next){
v=edge[e].v; cost=edge[e].cost;
if (!vis[v]&&dis[v]>dis[u]+cost){
dis[v]=dis[u]+cost;
que.push(pii(dis[v],v));
}
}
}
}
int dfs(int u,int d){//记忆化搜索
if (flag[u][d]){//③
zeroring=true;
return 0;
}
if (~dp[u][d]) return dp[u][d];
flag[u][d]=true; dp[u][d]=0;
int v,cost,vd;
for (int e=EHead[u];e&&!zeroring;e=Edge[e].Next){
v=Edge[e].v; cost=Edge[e].cost;
vd=dis[u]+d-cost-dis[v];//①
if (vd>=0) dp[u][d]=(dp[u][d]+dfs(v,vd))%p;//②、③
}
flag[u][d]=false;
if (u==1&&!d) return ++dp[u][d];
return dp[u][d];
}
int main(){
scanf("%d",&T);
while (T--){
init();
scanf("%d%d%d%d",&n,&m,&k,&p);
for (int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&cost);
addedge(i,u,v,cost);
}
dijsktra(1);
//dp[1][0]=1;
zeroring=false;
ans=dfs(n,0)%p;
for (int i=1;i<=k&&!zeroring;i++) ans=(ans+dfs(n,i))%p;
if (zeroring) puts("-1");
else printf("%d\n",ans);
}
return 0;
}
//突发奇想,突然想到反图跑最短路,原图记忆化搜索,原理相同,就不加注释了
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>,greater<pii> > que;
int T,n,m,k,p,u,v,cost,ans;
int eHead[110000],EHead[110000],dis[110000],dp[110000][61];
bool zeroring,vis[110000],flag[110000][55];
struct E{
int v,cost,Next;
} edge[210000],Edge[210000];
void init(){
memset(eHead,0,sizeof(eHead));
memset(EHead,0,sizeof(EHead));
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(vis));
memset(dp,-1,sizeof(dp));
}
void addedge(int i,int u,int v,int cost){
edge[i].v=v; edge[i].cost=cost;
edge[i].Next=eHead[u]; eHead[u]=i;
Edge[i].v=u; Edge[i].cost=cost;
Edge[i].Next=EHead[v]; EHead[v]=i;
}
void dijsktra(int s){
dis[s]=0;
que.push(pii(0,s));
int u,v,cost;
while (!que.empty()){
u=que.top().second;
que.pop();
if (vis[u]) continue;
vis[u]=true;
for (int e=eHead[u];e;e=edge[e].Next){
v=edge[e].v; cost=edge[e].cost;
if (!vis[v]&&dis[v]>dis[u]+cost){
dis[v]=dis[u]+cost;
que.push(pii(dis[v],v));
}
}
}
}
int dfs(int u,int d){
if (flag[u][d]){
zeroring=true;
return 0;
}
if (~dp[u][d]) return dp[u][d];
flag[u][d]=true; dp[u][d]=0;
int v,cost,vd;
for (int e=EHead[u];e&&!zeroring;e=Edge[e].Next){
v=Edge[e].v; cost=Edge[e].cost;
vd=dis[u]+d-cost-dis[v];
if (vd>=0) dp[u][d]=(dp[u][d]+dfs(v,vd))%p;
}
flag[u][d]=false;
if (u==n&&!d) return ++dp[u][d];
return dp[u][d];
}
int main(){
scanf("%d",&T);
while (T--){
init();
scanf("%d%d%d%d",&n,&m,&k,&p);
for (int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&cost);
addedge(i,v,u,cost);
}
dijsktra(n);
//dp[1][0]=1;
zeroring=false;
ans=dfs(1,0)%p;
for (int i=1;i<=k&&!zeroring;i++) ans=(ans+dfs(1,i))%p;
if (zeroring) puts("-1");
else printf("%d\n",ans);
}
return 0;
}