LOJ 2316「NOIP2017」逛公园 dijkstra+dp

题面

题目传送门

解法

开始认认真真学习NOIP原题

  • 讲两种不同的做法吧:
  • 第一种做法:求出从 1 1 1号点到其它所有点的距离和 n n n号点到其它所有点的距离(这个需要建出反向边之后来跑),然后我们设 f [ i ] [ j ] f[i][j] f[i][j]表示从 1 1 1走到点 i i i且当前走过的路径长度比 1 1 1 i i i的最短路大 j j j的方案数
  • 考虑这种做法如何转移,我们现在需要确定 f f f数组的更新顺序。当存在长度为 0 0 0的边的时候,距离1相同的顺序可能有先后变动。那么我们可以把所有长度为 0 0 0的边建成一个新图,对于这个新图我们做一遍拓扑排序,如果出现 0 0 0环且上面的点满足 d i s 1 [ i ] + d i s n [ i ] &lt; = d i s 1 [ n ] + K dis1[i]+disn[i]&lt;=dis1[n]+K dis1[i]+disn[i]<=dis1[n]+K,那么这时一定为 − 1 -1 1,否则按照 d i s 1 [ i ] + d i s n [ i ] dis1[i]+disn[i] dis1[i]+disn[i]作为第一关键字,拓扑序作为第二关键字转移就可以了
  • 具体的状态转移方程比较简单,假设当前的状态为 f [ x ] [ k ] f[x][k] f[x][k],现在有一条有向边 ( x , y , v ) (x,y,v) (x,y,v),那么 f [ x ] [ k ] f[x][k] f[x][k]这个状态就可以转移到状态 f [ y ] [ d i s 1 [ x ] + k + v − d i s 1 [ y ] ] f[y][dis1[x]+k+v-dis1[y]] f[y][dis1[x]+k+vdis1[y]]
  • 时间复杂度: O ( T m k ) O(Tmk) O(Tmk),实现起来比较繁琐
  • 第二种做法:这一种做法相对第一种来说比较简单。我们考虑反向地dp,即最后我们要求的是 ∑ i = 0 K f [ 1 ] [ i ] \sum_{i=0}^K f[1][i] i=0Kf[1][i]。转移和第一种方法的转移是一样的,这个我们可以使用记忆化搜索实现。那么如何判断 0 0 0环??如果一个状态已经在栈中,并且又被访问到,那么一定存在 0 0 0环。
  • 时间复杂度: O ( T m k ) O(Tmk) O(Tmk),实现起来较为简单
  • 以上两种做法均可通过此题,建议使用第二种做法
  • 如果要使最后的程序效率比较优秀,那么请尽量使用dijkstra算法。因为我比较懒,所以就只使用了spfa

代码

#include <bits/stdc++.h>
#define PI pair <int, int>
#define mp make_pair
#define inf 1 << 30
#define N 100001
using namespace std;
template <typename node> void chkmax(node &x, node y) {x = max(x, y);}
template <typename node> void chkmin(node &x, node y) {x = min(x, y);}
template <typename node> void read(node &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
int n, m, K, p, flag, dis[N], used[N], f[N][51], vis[N][51];
vector <PI> e[N], E[N];
void spfa() {
	for (int i = 1; i <= n; i++) dis[i] = inf, used[i] = 0;
	dis[n] = 0; queue <int> q; q.push(n);
	while (!q.empty()) {
		int x = q.front(); q.pop(); used[x] = 0;
		for (int i = 0; i < E[x].size(); i++) {
			PI tmp = E[x][i]; int k = tmp.first, v = tmp.second;
			if (dis[k] > dis[x] + v) {
				dis[k] = dis[x] + v;
				if (!used[k]) q.push(k), used[k] = 1;
			}
		}
	}
}
int dfs(int x, int y) {
	if (vis[x][y]) return flag = false, 0;
	if (f[x][y]) return f[x][y]; vis[x][y] = 1;
	int sum = 0;
	for (int i = 0; i < e[x].size(); i++) {
		PI tx = e[x][i];
		int k = tx.first, v = tx.second;
		int tmp = y + dis[x] - v - dis[k];
		if (tmp < 0 || tmp > K) continue;
		(sum += dfs(k, tmp)) %= p;
		if (!flag) return 0;
	}
	if (x == n && y == 0) sum = 1;
	return vis[x][y] = 0, f[x][y] = sum;
}
int main() {
	int T; read(T);
	while (T--) {
		read(n), read(m), read(K), read(p);
		for (int i = 1; i <= n; i++) e[i].clear(), E[i].clear();
		for (int i = 1; i <= m; i++) {
			int x, y, v; read(x), read(y), read(v);
			e[x].push_back(mp(y, v));
			E[y].push_back(mp(x, v));
		}
		spfa();
		memset(f, 0, sizeof(f)); int ans = 0;
		memset(vis, 0, sizeof(vis)); flag = true;
		for (int i = 0; i <= K; i++) (ans += dfs(1, i)) %= 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、付费专栏及课程。

余额充值