NOIP2017逛公园 记忆化搜索+判环+最短路

题目链接:洛谷-3953

主要思路:

由于这道题K的数值范围较小,故可以直接枚举K来求答案。答案就是起点到终点的为d,d+1,d+2,...,d+K的路径的条数总合。

           联想到之前一些在路径上有消耗的图论题,我们可以把这个K也当做是一个被消耗的东西,就像钱一样(以下就当钱讲好了)。故我们可以定义dp[i][j]为从i这个点出发走比他到终点的最短距离大j的路径有多少条。接下来就是状态转移的问题了 。
           记你的当前所在的点X到终点的最短距离为Dis1,现在你要走到点Y,他到终点的最短距离为Dis2,X到Y的边长为D。那么你从X走Y到终点的最短距离为Dis2+D,而你走点X到终点的最短路径(不用管有没有经过Y)为Dis1,故消耗的钱就是Dis2+D-Dis1,即为cost。故dp[X][j]=∑dp[Y][j-cost](若钱不够是不能走的,但是钱刚好花完是可以走的,也就是不要欠钱就可以了一直走下去)终止状态就是终点且剩余钱数为0,这样只有一种方案。(到达终点时若策策还有钱,就让策策去把钱正好花完,花不完是不能计数的)。
           那么就能发现如果能走到一个0环上且手上的钱不为负数,那么他可以沿着0环随便走多少步再到终点(0环上的点到终点的最短距离都是一样的),那么这样就得输出-1,但是前提条件是他能用手上的钱走到0环。
           判0环就让记录在这条路上你上次到达这个点还剩多少钱,若与这个点一样就说明这个点在0环上。(可能有些人会问,从0环走出来没办法正好把钱花完走向终点怎么办,出门少带钱就可以了(如果你没有和我一样的疑问可以忽略))
每个点到终点的最短距离就建一个方向图跑最短路就可以了。

AC代码:

#include<cstdio>
#include<cstring>
#include<queue>
#define M 200005
using namespace std;
int dis[M];
struct Graph {
	struct E {
		int to,nx,d;
	} edge[M];
	int tot,head[M];
	void Init() {memset(head,0,sizeof(head)),tot=0;}
	void Addedge(int a,int b,int d) {
		edge[++tot].to=b;
		edge[tot].nx=head[a];
		edge[tot].d=d;
		head[a]=tot;
	}
} G1,G2;
struct Dijstra {
	struct node {
		int id,d;
		bool operator <(const node &x)const {
			return d>x.d;
		}
		node(int id,int d):id(id),d(d) {}
	};
	priority_queue<node>Q;
	int cnt[M];
	bool mark[M];
	void solve(int st) {
		Q.push(node(st,0));
		memset(dis,63,sizeof(dis));
		memset(mark,0,sizeof(mark));
		dis[st]=0;
		while(!Q.empty()) {
			int now=Q.top().id;
			Q.pop();
			if(mark[now])continue;
			mark[now]=1;
			for(int i=G2.head[now]; i; i=G2.edge[i].nx) {//在反向图上跑每个点到终点的最短路 
				int nxt=G2.edge[i].to;
				if(dis[now]+G2.edge[i].d<dis[nxt]) {
					dis[nxt]=dis[now]+G2.edge[i].d;
					Q.push(node(nxt,dis[nxt]));
				}
			}
		}
	}
} S;
int P,K,n;
bool have_ring;
bool in_ring[M][55];
int dp[M][55];//dp[i][j]表示从i出发,比最短路多走的路程为j的方案总数
int dfs(int now,int now_k) {
	if(in_ring[now][now_k]) {
		have_ring=1;//判是否存在0环 
		return 0;
	}
	if(dp[now][now_k]>=0)return dp[now][now_k];//记忆化 
	in_ring[now][now_k]=1;
	int& res=dp[now][now_k];
	res=0;
	for(int i=G1.head[now]; i; i=G1.edge[i].nx) {
		int nxt=G1.edge[i].to,nxtd=now_k-(G1.edge[i].d-(dis[now]-dis[nxt]));//nxtd为剩下的钱 
		if(nxtd<0||nxtd>K)continue;//超出边界,不能走 
		res=(res+dfs(nxt,nxtd))%P;
		dfs(nxt,nxtd);
		if(have_ring)return 0;//如果有环就直接退出 
	}
	in_ring[now][now_k]=0;
	if(now==n&&now_k==0)res=1;//到达终点放在这里判,防止终点在0环上。注意只有钱花完且到达终点才停止。 
	return res;
}
void Init() {
	have_ring=0;
	memset(in_ring,0,sizeof(in_ring));
	memset(dp,-1,sizeof(dp));
	G1.Init(),G2.Init();
}
int main() {
	int T;
	scanf("%d",&T);
	while(T--) {
		Init();
		int m;
		scanf("%d%d%d%d",&n,&m,&K,&P);
		for(int i=1; i<=m; i++) {
			int a,b,d;
			scanf("%d%d%d",&a,&b,&d);
			G1.Addedge(a,b,d);//建正向边 
			G2.Addedge(b,a,d);//建反向边 
		}
		S.solve(n);//预处理终点到每个点的最短距离 
		int ans=0;
		for(int i=0; i<=K; i++) {//循环从0开始 
			ans=(ans+dfs(1,i))%P;
			if(have_ring)break;
		}
		if(have_ring)puts("-1");
		else printf("%d\n",ans);
	}
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值