题解 P3953 【逛公园】
最短路+记忆化搜索
最短路:
每一组数据的边数=点数*2,这是一个稀疏图
直接上spfa
问那些说会卡spfa的人两个问题:
1.NOIP有卡过spfa吗?(归程是NOI2018)
2.数据范围是明显的稀疏图,出题人的潜台词是什么?
记忆化搜索
我们用dfs(x,l)表示当前递归到x个点,现在还有l距离
转移方式:dfs(x,l)=dfs(y,d[x]+l-d[y]-z)
我用了动态数组+结构体存边,要存两次单向边 (这句话自己理解一下,不是存一次双向边)
如果检测到0环,就返回-1,否则返回方式数
别的
1.每步都要取余这种常识性的我就不讲了
2.多组数据要初始化
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll t,d[100001],f[100001][51],n,m,k,p;
bool working[100001][51];
struct node{
ll to,edge;
//to表示下一个点
//edge表示长度
};
vector<node> head[100001],h[100001];
//head存正向边
//h存反向边
ll dfs(ll x,ll l){
ll ans=0;
if(l<0||l>k) return 0;//超出范围还没走到就返回
if(working[x][l]){//找到0环
working[x][l]=false;
return -1;
}
if(f[x][l]!=-1) return f[x][l];//这个点已经走过了
working[x][l]=true;
for(ll i=0; i<h[x].size(); i++){
ll y=h[x][i].to,z=h[x][i].edge,val=dfs(y,d[x]+l-d[y]-z);
//y表示下一个格子
//z表示剩下的距离
if(val==-1){//找到0环
working[x][l]=false;
return -1;
}
ans=(ans+val)%p;
}
working[x][l]=false;//去过了
if(x==1&&l==0) ans++;//找到,并且距离用完
f[x][l]=ans;//存下结果
return ans;
}
inline void spfa(){//最短路,不讲了
memset(d,0x3f,sizeof(d));
memset(f,-1,sizeof(f));
queue<ll> q;
q.push(1);
d[1]=0;
while(!q.empty()){
ll x=q.front(); q.pop();
for(ll i=0; i<head[x].size(); i++){
ll y=head[x][i].to,z=head[x][i].edge;
if(d[y]>d[x]+z){
d[y]=d[x]+z;
q.push(y);
}
}
}
}
ll work(){
scanf("%lld%lld%lld%lld",&n,&m,&k,&p);
for(ll i=1; i<=n; i++){
head[i].clear(); h[i].clear();
}
while(m--){
ll x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
head[x].push_back(node{y,z});//存正边
h[y].push_back(node{x,z});//存反边
}
spfa();
ll ans=0;
for(ll i=0; i<=k; i++){
ll val=dfs(n,i);//调用
if(val==-1) return -1;//找到0环直接退出
ans=(ans+val)%p;//结果增加
}
return ans;
}
int main(){
scanf("%lld",&t);
while(t--) printf("%lld\n",work());
return 0;
}