洛谷P3953 NOIP2017D1T3逛公园

讲实话,这题我觉得比D2T3难,糊脸

一句话题意,让你求长度小于MinDis(1,n)+k的路径条数,若有无穷条,输出-1
话说看完题第一反应是为什么会有无穷条,后来看见数据有零环,感觉人生崩塌了

先无脑跑一个最短路,求出最短路,以及每个点到1的最小距离,可以spfa,不卡
然后因为题目要求一定要回到N点,那么我们反向建图,从N开始bfs跑出能到N的每个点
这样后面遇到不合法的直接continue好了

接下来是重点,怎么设计dp,其实数据范围给了启发,n=100000,k=50,妥妥的
那么怎么设计状态和转移呢,我个人是这样理解的,既然是长度小于dis(1,n)+k
那么这条路径肯定是在最短路上选一个点分叉出去,那么这就会产生一些冗余路径
我们记录冗余路径长度,小于K就可以继续dfs,不然直接走接下来的最短路径好了
那么dp[i][j]表示当前i点还剩j可以浪费时的符合要求的路径条数
所以对于边i d p [ t o i ] [ j ] + = d p [ f r o m i ] [ j + w i − ( d i s [ t o i ] − d i s [ f r o m i ] ) ] dp[to_i][j]+=dp[from_i][ j+w_i-(dis[to_i]-dis[from_i]) ] dp[toi][j]+=dp[fromi][j+wi(dis[toi]dis[fromi])]
那么0环怎么判断呢,因为是记忆化循环,0环就会死循环,特判一下好了
过程大概是
最 短 路 − > 可 行 点 − > 记 忆 化 搜 索 最短路->可行点->记忆化搜索 >>
代码,人丑常数大,会被卡一个点,只能开O2过了

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int maxn = 100007;
const int INF = 2147483647;
const int maxk = 51;
int n,m,k,p,len,dis[maxn],alive[maxn];
struct node
{
	int to,next,w;
}edge[maxn*2],abedge[maxn*2];
int cnt,head[maxn],abcnt,abhead[maxn],ans[maxn][maxk],vis[maxn][maxk];
inline int read() 
{
    char ch = getchar();int ret=0;
    while(ch<'0'||ch >'9') ch=getchar();
    while(ch<='9'&&ch>='0') ret=ret*10+ch-'0',ch=getchar();
    return ret;
}

void add(int from,int to,int w)
{
	edge[++cnt].to=to;
	edge[cnt].next=head[from];
	edge[cnt].w=w;
	head[from]=cnt;
	abedge[++abcnt].to=from;
	abedge[abcnt].next=abhead[to];
	abedge[abcnt].w=w;
	abhead[to]=abcnt;
}


void spfa()
{	
	queue<int>q;
	int used[maxn];
	for(int i=1;i<=n;i++)dis[i]=INF;
	memset(used,0,sizeof(used));
	dis[1]=0;used[1]=1;
	q.push(1);
	while(!q.empty())
	{
		int f1=q.front();
		q.pop();used[f1]=0;
		for(int i=head[f1];i;i=edge[i].next)
		{
			int to=edge[i].to,w=edge[i].w;
			if(dis[to]>dis[f1]+w)
			{
				dis[to]=dis[f1]+w;
				if(!used[to])q.push(to),used[to]=1;
			}
		}
	}
}

void bfs()
{
	int used[maxn];
	memset(used,0,sizeof(used));
	queue<int>qp;
	qp.push(n);used[n]=1;
	while(!qp.empty())
	{
		int f1=qp.front();
		qp.pop();
		alive[f1]=1;
		for(int i=abhead[f1];i;i=abedge[i].next)
		{
			int to=abedge[i].to;
			if(!used[to])qp.push(to),used[to]=1;
		}
	}
}

int dps(int u,int rest)
{
	if(rest<0)return 0;
	else if(vis[u][rest])return -INF;
	else if(ans[u][rest]!=-1)return ans[u][rest];
	else
	{
		int re=u==n?1:0;
		vis[u][rest]=1;
		for(int i=head[u];i;i=edge[i].next)
		{	
			int to=edge[i].to;
			if(!alive[to])continue;
			int w=edge[i].w;
			int waste=w-dis[to]+dis[u];
			int ret=dps(to,rest-waste);
			if(ret==-INF)return -INF;
			else
				re=(re+ret)%p;
		}
		ans[u][rest]=re;
		vis[u][rest]=0;
		return re;
	}
}


int main()
{
	int T;
	T=read();
	while(T--)
	{
		memset(alive,0,sizeof(alive));
		memset(head,0,sizeof(head));
		memset(abhead,0,sizeof(abhead));
		memset(edge,0,sizeof(edge));
		memset(abedge,0,sizeof(abedge));
		memset(vis,0,sizeof(vis));
		memset(ans,-1,sizeof(ans));		
		cnt=abcnt=0;
		n=read(),m=read(),k=read(),p=read();
		for(int i=1;i<=m;i++)
		{
			int x,y,z;
			x=read(),y=read(),z=read();
			add(x,y,z);
		}
		spfa(),bfs();
		int ans1=dps(1,k);
		if(ans1!=-INF)cout<<ans1<<endl;
		else cout<<"-1"<<endl;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!感谢你的提问。根据你的要求,我将为你解答关于NOIP2017普及t3问题的内容。 在这道题目中,我们需要解决一个关于棋盘的问题。题目描述了一个n×m的棋盘,每个格子上有一个非负整数。开始时,小明站在左上角的格子上,他可以向右或向下移动一步,每次移动的代价为目标格子上的数值。我们需要找到一条从左上角到右下角的路径,使得移动的总代价最小。 解决这个问题的一个常见的方法是使用动态规划(Dynamic Programming)。我们可以创建一个二维数组dp,其中dp[i][j]表示从起点到达坐标为(i, j)的格子时的最小代价。然后,我们可以按照从左上角到右下角的顺序依次计算dp数组的值。 具体的计算方法如下: 1. 首先,我们可以初始化dp数组的第一行和第一列,即dp[0][j]和dp[i][0],它们表示从起点到达第一行和第一列的格子时的最小代价。初始化的方法是累加前面的格子的代价。 2. 接下来,我们可以使用一个双重循环,从(1, 1)开始遍历整个棋盘。对于每个格子(i, j),我们可以选择从上方格子(i-1, j)或左方格子(i, j-1)中选择一个代价较小的路径,并加上当前格子的代价。即dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]。 3. 最后,当我们计算完dp数组的所有值后,dp[n-1][m-1]即为从起点到达右下角的格子时的最小代价。 这样,我们就可以得到从左上角到右下角的最小代价。希望能对你的问题有所帮助!如果你还有其他问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值