洛谷P3953 逛公园——spfa,反图上dp(记忆化搜索),及TLE的一个可能原因

题目:https://www.luogu.org/problem/P3953

分析:

建原图求最短路,建反图dp。

spfa:

求出点1到点 i 的最小距离dis[i]。

记忆化搜索:

dp。记忆化搜索相对好写。

定义dp[i][t]表示从1号点走到i号点,路径长度为 dis[i]+t 的方案总数。

如果dp[i][t]的值已经求过,则直接返回。

本人第一个代码用了以下记忆化搜索的代码:

if(dp[u][t])return dp[u][t]; 

导致TLE。

原因在于没有注意到0边,当dp[i][t]=0时有可能dp[i][t]已经求过,按上面的代码,是将继续重复求值,从而TLE。

作以下修改,就AC了本题。

if(dp[u][t]!=-1)return dp[u][t]; 

状态转移方程:

dp[u][t]=sigema(dp[v][ dis[u]-dis[v]-val(v,u)+t ],反图边(v,u)中,v为u的后继。

剪枝一:

t<0或t>K,return 0

剪枝二:

dis[v]>dis[n]+K,continue

AC代码:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int MaxN=1e5+1,MaxM=2e5+1;
int T,n,m,K,p;
int dis[MaxN],dp[MaxN][52];
//dis[u]表示从1号节点走到u号节点的最短路长度,
//f[u][k]表示从1号节点走到u号节点,路径长度为dis[u]+k的方案总数。
struct Edge{
    int from,to,val,next;
}edge1[MaxM],edge2[MaxM]; 
inline int read()
{
    int X=0,w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
int tot,cns,head1[MaxN],head2[MaxN];
void add1(int from,int to,int val){
    edge1[++tot].next=head1[from];
    edge1[tot].to=to;
    edge1[tot].val=val;
    head1[from]=tot;
}
void add2(int from,int to,int val){
    edge2[++cns].next=head2[from];
    edge2[cns].to=to;
    edge2[cns].val=val;
    head2[from]=cns;
}
int head,tail,que[MaxN*2+7],u,v,val;
int exist[MaxN]; 
void spfa(){
    dis[1]=0;
    int head=0,tail=1;
    que[1]=1;exist[1]=1;
    while(head!=tail){
        ++head;
        head=(head-1)%(MaxN*2+5)+1;//循环队列,大小开到2n+5 
        u=que[head];
        exist[u]=0;//表示从队列取出 
        for(int i=head1[u];i;i=edge1[i].next){
            v=edge1[i].to,val=edge1[i].val;
            if(dis[v]>dis[u]+val){
                dis[v]=dis[u]+val;
                if(!exist[v]){//加入队列
                    ++tail;
                    tail=(tail-1)%(MaxN*2+5)+1; 
                    que[tail]=v;
                    exist[v]=1;
                }
            }
        }
    }   
}
int ins[MaxN][52]; 
int dpp(int u,int k){
    if(k<0||k>K)return 0;
    if(ins[u][k]){//出现0环
        ins[u][k]=0;
        return -1;
    }      
    if(dp[u][k]!=-1)return dp[u][k]; 
    ins[u][k]=1;
    int ans=0;
    for(int i=head2[u];i;i=edge2[i].next){
        int v=edge2[i].to,val=edge2[i].val;
        if(dis[v]>dis[n]+K)continue;//剪枝
        int tmp=dpp(v,dis[u]-dis[v]+k-val);
        if(tmp==-1){
            ins[u][k]=0;
            return -1;
        }
        ans=(ans+tmp)%p;
    }
    ins[u][k]=0;
    dp[u][k]=ans;
    return ans;
}
inline void inin(){
	memset(head1,0,sizeof(head1));
    memset(head2,0,sizeof(head2));
    memset(exist,0,sizeof(exist));
    memset(dp,-1,sizeof(dp));//不能初始化为0。因为0边 
    tot=cns=0;
    memset(dis,0x3f,sizeof(dis));
    memset(ins,0,sizeof(ins));   
}
inline int work(){
	inin();
    n=read();m=read();K=read();p=read();  
    for(int x,y,z,i=1;i<=m;i++){
        x=read();y=read();z=read();
        add1(x,y,z); 
        add2(y,x,z); 
    }      
    spfa();
    int ans=0;
    dp[1][0]=1;
    for(int t=0;t<=K;t++){
        int res=dpp(n,t);
        if(res==-1) return -1;
        ans=(ans+res)%p;
    }
    return ans; 	
}
int main(){
    T=read();
    while(T--) printf("%d\n",work());
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值