P3953题解 记忆化搜索
做完这题后我看了大部分题解(我太弱了,交了15次才过),发现大多数题解都被新数据hack掉了,按时间排序后发现前几位大佬的做法都十分优越,但没有一个是像我一样跑一次最短路+没有剪枝的记忆化搜索就跑过的,就想来写一篇题解。
这题的部分分带有十足的引导性,但前面的部分分以前的题解已经讲的很清楚了,所有我们就直接从没有零边的情况开始(也就是 70pts )。
发现 k 最大才50!
看到 k 比较小,就用 k 作为动规的一维,再考虑另一维,可以选择点或边——果断选 A (从来没有用边转移过,也可能是我没想到)。
怎么 dp ?
想这应该是想好 dp 的维度后应最优先考虑的问题,先仔细读题,我们求路程的总长度要满足条件
再将
转移条件具备,目标达成
JOJO,这是我最后的方程了:
(最后不要忘了
实际做的时候我们枚举 又是废话)。
重点
我们在记忆化搜索和求最短路时可以只建立单向边,毕竟都不会再往回跑,然后就是别用同一张图,记忆化搜索时要反向建边,从终点开始搜,这样才可以保证
看到这里可以先去尝试实现以一下(我知道这是不可能的)。
最后考虑有零边,也就是无限的情况,其实就是考虑有没有零边所构成的环(没有零环就一定是有限的),判断方法异常简单,就是像 SPFA dfs 判负环一样,用一个
以上就是这题的解法,其他大佬有 tarjan,topu,dfs+ 剪枝...但只有我的方法只用了一次最短路+无剪枝记忆化搜索就过了的(让我小骄傲一下),但时间普遍比其他方法慢 100-300ms 所以建议还是多看几篇题解(真是道好题)
最后附上 AC 记录和代码
AC了哦
#include<bits/stdc++.h>
using namespace std;
const int maxn=500005;
const int maxk=55;
inline int read(){
int x=0; char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x;
}
int n,p;
int a[maxn],b[maxn],nxt[maxn],head[maxn],cnt;
int A[maxn],B[maxn],NXT[maxn],HEAD[maxn],CNT;
bool flag;
int dp[maxn][maxk];
inline void addline(int x,int y,int z){
a[++cnt]=y,b[cnt]=z,nxt[cnt]=head[x],head[x]=cnt;
}
inline void ADDLINE(int x,int y,int z){
A[++CNT]=y,B[CNT]=z,NXT[CNT]=HEAD[x],HEAD[x]=CNT;
}
int dis[maxn];
struct nn{
int d,id;
bool operator < ( const nn &A ) const{ return d>A.d; };
};
priority_queue<nn> pq;
inline void dijkstra(int s){
for(register int i=1;i<=n;++i) dis[i]=0x3f3f3f3f;
dis[s]=0; pq.push((nn){0,s});
while(!pq.empty()){
nn now=pq.top(); pq.pop();
int val=now.d,u=now.id;
if(dis[u]<val) continue;
for(register int p=head[u]; p ;p=nxt[p]){
int v=a[p];
if(dis[v]>dis[u]+b[p]){
dis[v]=dis[u]+b[p];
pq.push((nn){dis[v],v});
}
}
}
}
bool vis[maxn][maxk];
int dfs(int now,int k){
// cout<<now<<" "<<k<<endl;
if(~dp[now][k]) return dp[now][k];
dp[now][k]=0,vis[now][k]=1;
for(register int i=HEAD[now]; i ;i=NXT[i]){
int v=A[i];
int x=dis[now]-dis[v]+k-B[i];
if(x<0) continue;
if(vis[v][x]) flag=1;
dp[now][k]=(dp[now][k]+dfs(v,x))%p;
}
vis[now][k]=0;
return dp[now][k];
}
/*
dp[p][x]->dp[now][k]
x=dis[now]-dis[p]+k-b[p]
*/
int m,k;
int main(){
int T=read();
while(T--){
n=read(),m=read(),k=read(),p=read();
cnt=CNT=flag=0;
memset(head,0,sizeof head);
memset(HEAD,0,sizeof HEAD);
memset(dp,-1,sizeof dp);
for(register int i=1;i<=m;++i){
int u=read(),v=read(),w=read();
addline(u,v,w),ADDLINE(v,u,w);
}
dijkstra(1);
for(register int i=0;i<=k;++i) dfs(n,i);
if(flag) { printf("-1\n"); continue; }
int ans=0;
memset(dp,-1,sizeof dp); dp[1][0]=1;
for(register int i=0;i<=k;++i){
ans=(ans+dfs(n,i))%p;
}
printf("%d\n", ans);
}
return 0;
}