「Luogu P3953」[NOIP2017 提高组] 逛公园

题目

策策同学特别喜欢逛公园。公园可以看成一张 \(N\) 个点 \(M\) 条边构成的有向图,且没有 自环和重边。其中 \(1\) 号点是公园的入口,\(N\) 号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。

策策每天都会去逛公园,他总是从 \(1\) 号点进去,从 \(N\) 号点出来。

策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果 \(1\) 号点到 \(N\) 号点的最短路长为 \(d\),那么策策只会喜欢长度不超过 \(d + K\) 的路线。

策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?

为避免输出过大,答案对 \(P\) 取模。

如果有无穷多条合法的路线,请输出 \(-1\)

Luogu

分析

这题看上去像是 DP. 记 \(f[i][j]\) 表示从 \(1\) 号点到 \(i\) 号点, 路径长度为 \(j\) 的方案数. 不过这显然存不下. 不难发现 \(j\) 只可能取 \([\mathrm{dis}_{i}, \mathrm{dis}_{i}+k]\) 这个区间的值, 而区间长度为 \(k+1\leq 51\) 很小, 因此我们可以先跑一遍 Dijkstra 然后再令 \(f[i][j]\) 表示从 \(1\) 号点到 \(i\) 号点, 路径长度为 \(j+\mathrm{dis}_{i}\) 的方案数.

以什么顺序遍历所有结点? 在 dfs 的过程中进行状态转移. 假设当前有 \((u, v, w)\) 这条边, 正在枚举的 \(u\) 的路径长度为 \(\mathrm{dis}_{u}+j\), 那么状态转移方程为 \(f[v][j+\mathrm{dis}_{u}+w-\mathrm{dis}_{v}] = f[v][j+\mathrm{dis}_{u}+w-\mathrm{dis}_{v}] + f[u][j]\), 也就是说, 我们更新的答案是还未访问的 \(f[v][j+\mathrm{dis}_{u}+w-\mathrm{dis}_{v}]\), 同时 \(f[u][j]\) 也不是完整的, 我们希望用 \(v\) 的答案来更新 \(u\), 那就建一个反图, 在反图上 dfs 更新答案即可, 此时对于原图中的边 \((u, v, w)\), 我们有总的状态转移方程:

\[f[u][j] = \sum_{v \vert (u,v,w) \in E} f[v][j+\mathrm{dis}_{u}+w-\mathrm{dis}_{v}] \]

一个结点会被多次遍历到怎么办? 不从不同的路径到达怎么统计路径方案数嘛.

遇到环了怎么办? 用一个栈存储当前遍历的路径结点, in[] 数组判断是否在栈中就能判环了 qwq (不过我们只需要用到 in[] 的信息不需要用到栈)

如果是零环怎么办?此时再次回到结点 \(u\) 时枚举的路径长度仍然是 \(\mathrm{dis}_{u}+j\), 那么我们给 in 数组再加一维, 用 in[u][j] 判定之前 dfs 栈信息即可, 如果有零环则直接返回 -1, 答案显然是 \(\infty\).

代码

#include <bits/stdc++.h>

using PII = std::pair<int, int>;

constexpr int N = 1e5 + 5;
constexpr int M = 2e5 + 5;
constexpr int INF = 0x3f3f3f3f;

int n, m, k, p;
std::vector<PII> e1[N], e2[N];
int dis[N], f[N][55];
bool flag;
bool vis[N], in[N][55];

void insert(int u, int v, int w) {
	e1[u].push_back({v, w}), e2[v].push_back({u, w});
}

void dijkstra(int s) {
	for (int i = 1; i <= n; ++i) dis[i] = INF, vis[i] = 0;
	std::priority_queue<PII> q;
	q.push({0, s});
	dis[s] = 0;
	while (!q.empty()) {
		int u = q.top().second;
		q.pop();
		if (vis[u]) continue;
		for (auto it : e1[u]) {
			int v = it.first, w = it.second;
			if (dis[v] > dis[u] + w) {
				dis[v] = dis[u] + w;
				q.push({-dis[v], v});
			}
		}
	}
}

int dfs(int u, int len) {
	if (len > k || len < 0) {
		return 0;
	}
	if (in[u][len]) {
		flag = 1;
		return -1;
	}
	if (f[u][len]) {
		return f[u][len];
	}

	in[u][len] = 1;
	int sum = 0;
	for (auto it : e2[u]) {
		int v = it.first, w = it.second;
		sum = (sum + dfs(v, len + dis[u] - dis[v] - w)) % p;
		if (flag) {
			return -1;
		}
	}

	in[u][len] = 0;
	if (u == 1 && len == 0) sum++; // 别忘了边界情况
	return f[u][len] = sum;
}

void solve() {
	std::cin >> n >> m >> k >> p;
	for (int i = 1; i <= n; ++i) {
		e1[i].clear(), e2[i].clear();
	}
	for (int i = 1, u, v, w; i <= m; ++i) {
		std::cin >> u >> v >> w;
		insert(u, v, w);
	}

	dijkstra(1);

	memset(f, 0, sizeof(f));
	memset(in, 0, sizeof(in));
	int ans = 0;
	flag = 0;
	for (int i = 0; i <= k; ++i) {
		int tmp = dfs(n, i);
		if (flag) {
			std::cout << "-1\n";
			return;
		} else {
			ans = (ans + tmp) % p;
		}
	}

	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int T;
	std::cin >> T;
	while (T--) {
		solve();
	}
	return 0;
}
posted @ 2024-11-26 14:34  小蒟蒻hlw  阅读(17)  评论(0)    收藏  举报