考前肝蛇题(luoguP3953逛公园) NOIP 2017

2 篇文章 0 订阅
2 篇文章 0 订阅

逛公园

题目链接

1.题目意思:

给一个有向图,求1—>n的路径中,路径长度小于dis[n]+k的路径数(min[i]表示1–>i的最短路长度);

2.解题思路

30分
对于k=0的数据考虑最短路计数即可
100分
设f[u][j]表示dis(1,u)(这里dis仅表示1—>u的任意路径长度)<=min[u]+k的路径总数
这个定义很关键,网上许多题解的定义并不明确,还是自己太菜。
显然min可以SPFA求出,对于f就得进行dp。
递推式很关键,看了许多题解都输草草概述,这里简单证明一下
对于u->v的边(原图正向)f[v]需要由f[u]递推而来,
定义f[u][j]: dis(1,u)<=min[u]+ju
f[v][j]: dis(1,v)<=min[v]+jv

还需要知道,走u–>v这条边对于min[v]来说多走了min[u]+len(边长)-min[v],画图感受一下即可
所以对于ju和jv有:
ju+min[u]+len-min[v]=jv,表示u—>v在u的基础上又多走了min[u]+len-min[v],于是移项得ju=jv+min[v]-min[u]-len

于是dp的递推式可以列出了:
对于u–>v
f [ v ] [ j ] = ∑ u − > v f [ u ] [ j + m i n [ v ] − m i n [ u ] − l e n ] f[v][j]= \sum_{u->v}^{}{f[u][j+min[v]-min[u]-len]} f[v][j]=u>vf[u][j+min[v]min[u]len]

于是就可以dp了,这里对于反向图进行记忆化搜索,为什么是反图呢,这样可以保证每一个便利的路程都可以抵达n,不会有到不了的情况进行多余的搜索

搜索的时候特别注意判断0环的情况,(即一个点被搜到了两次)
下面是代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
#define MAXN 100010
using namespace std;
struct Edge{int to,l;};
vector<Edge> E[MAXN],UE[MAXN];
int T,n,m,p,k,dis[MAXN],vis[MAXN],f[MAXN][55],working[MAXN][55];
queue<int> q;
void SPFA(){
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	q.push(1); dis[1]=0; vis[1]=1;
	while(!q.empty()){
		int u=q.front(); q.pop(); vis[u]=0;
		for(int i=0;i<E[u].size();i++){
			int v=E[u][i].to,d=E[u][i].l;
			if(dis[v]>dis[u]+d){
				dis[v]=dis[u]+d;
				if(!vis[v]) vis[v]=1,q.push(v); 
			}
		}
	}
}
int dfs(int u,int res){
	int ans=0; 
	if(res<0||res>k) return 0;//不合法不应影响答案 
	if(working[u][res]){
		working[u][res]=0;
		return -1;
	}//0环 
	if(f[u][res]) return f[u][res];//记忆化 
	working[u][res]=1;
	for(int i=0;i<UE[u].size();i++){
		int v=UE[u][i].to,d=UE[u][i].l;
		int val=dfs(v,res-(dis[v]+d-dis[u]));
		if(val==-1){
			working[u][res]=0;
			return -1;
		}
		ans=(ans+val)%p;
	}//关键dp 
	working[u][res]=0;
	if(u==1&&res==0) ans++;
	f[u][res]=ans;
	return ans;
}//以上回溯的时候将working置0,后面就不用memset了 
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d%d",&n,&m,&k,&p);
		for(int i=1;i<=n;i++) E[i].clear(),UE[i].clear();
		for(int i=1;i<=m;i++){
			int u,v,w; scanf("%d%d%d",&u,&v,&w);
			E[u].push_back((Edge){v,w}),UE[v].push_back((Edge){u,w});
		}
		SPFA();//最短路 
		memset(f,0,sizeof(f));
		int ans=0,flag=1;
		for(int i=0;i<=k;i++){
			int val=dfs(n,i);
			if(val==-1){
				flag=0;
				break;
			} 
			else ans=(ans+val)%p;
		}
		if(!flag) 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、付费专栏及课程。

余额充值