P3953 [NOIP2017 提高组] 逛公园 (dp + 最短路)

P3953 [NOIP2017 提高组] 逛公园

求有向图中 1n 的路径中长度小于等于 dis(1,n)+k 的方案数。dis(1,n) 表示最短路。k50

部分分 k=0,直接最短路计数即可。

我们发现有向图中存在后效性,不好动态规划,但我们仔细思考后,在不存在 0 边的情况下,设 f(u,k) 表示从 1 走到 u 时长度为 k 的方案数,若存在 f(v,j) 需要转移到它身上,那么有 kj,不存在后效性。所以最简单的:

f(u,k)f(v,k+w(u,v))

考虑优化 k 这一维,我们发现 k 很小,我们只关心 dis(1,n)+k 以内的路径,并且对于 1u 的路径,长度一定大于等于 dis(1,u)。所以状态写成 f(u,k) 表示从 1 走到 n 时长度为 dis(1,u)+k 的方案数。

假设 f(u,k) 可以转移到 f(v,x),那么有

dis(1,u)+k+w(u,v)=dis(1,v)+x

x=dis(1,u)+k+w(u,v)dis(1,v)

所以 f(u,k)f(v,dis(1,u)+k+w(u,v)dis(1,v))

70pts:此时我们转移就可以把按 dis 值从小到大排序转移(对于无 0 边)。

100pts:一样有无后效性,因为对于 k=x 时,dis(1,u)+w(u,v)=dis(1,v) 为最短路上的拓扑序,所有最短路构成的图是 DAG;而其他情况则是分层图上不同层的转移,同样有无后效性。

但有一个特殊情况就是图中 1n 的路径中存在 0 环,特判一下就可以转移了。可以用记忆化搜索,实现起来细节不多。

总结:特殊性质的优化,变为无后效性问题

#include <bits/stdc++.h>
typedef long long ll;
int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') f = -1;
		c = getchar();
	}
	while(isdigit(c)) {
		x = (x << 3) + (x << 1) + (c - '0');
		c = getchar();
	} 
	return x * f;
}
bool flg;
int t, n, m, k, p, cnt, cnt2, ans;
int h[100010], h2[100010], dis[100010], vis[100010], f[100010][51], ins[100010][51];
struct node {
	int to, nxt, w;
} e[200010], e2[200010];
void add(int u, int v, int w) {
	e[++cnt].to = v;
	e[cnt].nxt = h[u];
	e[cnt].w = w;
	h[u] = cnt;
}
void add2(int u, int v, int w) {
	e2[++cnt2].to = v;
	e2[cnt2].nxt = h2[u];
	e2[cnt2].w = w;
	h2[u] = cnt;
}
struct ac {
	int v, w;
	friend bool operator < (ac a, ac b) {
		return a.w > b.w;
	}
} tmp;
void dij(int s) {
	std::priority_queue<ac> q;
	for(int i = 1; i <= n; i++) dis[i] = 0x3f3f3f3f, vis[i] = 0;
	dis[s] = 0;
	q.push({s, 0});
	while(!q.empty()) {
		int u = q.top().v;
		q.pop();
		if(vis[u]) continue;
		vis[u] = 1;
		for(int i = h[u]; i; i = e[i].nxt) {
			int v = e[i].to;
			if(dis[v] > dis[u] + e[i].w) {
				dis[v] = dis[u] + e[i].w;
				tmp.v = v, tmp.w = dis[v];
				q.push(tmp);
			}
		}
	}
}
int dp(int u, int j) {
	if(j < 0) return 0;
	if(ins[u][j]) {
		flg = 1;
		return 0;
	}
	if(f[u][j]) return f[u][j];
	ins[u][j] = 1;
	int ret = 0;
	for(int i = h2[u]; i; i = e2[i].nxt) {
		int v = e2[i].to;
		ret = (ret + dp(v, dis[u] + j - e2[i].w - dis[v])) % p;
		if(flg) return 0;
	}
	ins[u][j] = 0;
	return f[u][j] = ret;
}
void Solve() {
	t = read();
	while(t--) {
		n = read(), m = read(), k = read(), p = read();
		for(int i = 1; i <= m; i++) {
			int u = read(), v = read(), w = read();
			add(u, v, w), add2(v, u, w);
		}
		dij(1);
		ans = 0, flg = 0;
		memset(f, 0, sizeof(f));
		memset(ins, 0, sizeof(ins));		
		dp(1, 0);
		f[1][0] = 1;
		for(int i = 0; i <= k; i++) {
			ans = (ans + dp(n, i)) % p;
		}
		if(flg) std::cout << "-1\n";
		else std::cout << ans << "\n";
		for(int i = 1; i <= n; i++) {
			h[i] = h2[i] = 0;
		}
		cnt = cnt2 = 0;
	}
}

int main() {
	
	Solve();

	return 0;
}
posted @   Fire_Raku  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示