luogu_P3953 逛公园 记忆化搜索+最短路
luogu_P3953 逛公园
记忆化搜索+最短路
题目链接
边权有0而且可能有环,所以可能出0环
出0环的话就有无限条路径了(因为可以绕着这个环一直转悠)
所以先正着跑spfa
然后反向建边
记忆化搜索
\(f[i][j]\)表示到\(i\)点,还有比最短路增加\(j\)的余额的方案数。
真不好理解
再记录一个\(vis[i][j]\)数组,\(i\),\(j\)的意义相同
但是这个存的是0/1,表示是不是之前访问过
假如之前访问过必然是0环(因为j没有减小)
向下转移的就是\(j+dis[x]-dis[y]-len(x,y)\)
表示用\(x\),\(y\)之间的边代替最短路他们之间的距离能消耗多少\(j\)
又不好理解
看代码吧
代码如下:
#include<bits/stdc++.h>
using namespace std;
int T;
namespace wk{
const int maxk=60,maxn=100010;
int n,m,k,p,dis[maxn],inq[maxn],vis[maxn][maxk],f[maxn][maxk];
struct node{
int to,dis;
node(){}
node(int _to,int _dis){to=_to;dis=_dis;}
};
vector<node> tu[maxn],ftu[maxn];
//DP
inline int dp(int now,int ls){
int res=0;
if(ls<0 || ls>k) return 0;
if(vis[now][ls]){
vis[now][ls]=0;return -1;
}
if(~f[now][ls]) return f[now][ls];
vis[now][ls]=1;
for(int i=0;i<ftu[now].size();i++){
int y=ftu[now][i].to,z=ftu[now][i].dis;
int val=dp(y,dis[now]+ls-dis[y]-z);
if(val==-1){
vis[now][ls]=0;return -1;
}
(res+=val)%=p;
}
vis[now][ls]=0;
if(now==1 && ls==0) res++;
f[now][ls]=res;
return res;
}
void main(){
scanf("%d%d%d%d",&n,&m,&k,&p);memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++) tu[i].clear(),ftu[i].clear();
for(int x,y,c,i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&c);
tu[x].push_back(node(y,c));ftu[y].push_back(node(x,c));
}
//spfa
memset(dis,0x3f,sizeof(dis));memset(inq,0,sizeof(inq));
queue<int> q;q.push(1);dis[1]=0;inq[1]=1;
while(q.size()){
int x=q.front();q.pop();inq[x]=0;
for(int i=0;i<tu[x].size();i++){
int y=tu[x][i].to,z=tu[x][i].dis;
if(dis[y]>dis[x]+z){
dis[y]=dis[x]+z;
if(!inq[y]){
q.push(y);inq[y]=1;
}
}
}
}
int ans=0;
for(int i=0;i<=k;i++){
int now=dp(n,i);
if(now==-1){printf("-1\n");return;}
(ans+=now)%=p;
}
printf("%d\n",ans);
return;
}
}
int main()
{
scanf("%d",&T);while(T--){wk::main();}
return 0;
}