Noip 2017 逛公园

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

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

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

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

为避免输出过大,答案对PP取模。

如果有无穷多条合法的路线,请输出-1−1。

 

题解:

这道题首先看到最短路先搞一个堆优

然后看到k只有50

嗯嗯嗯好像就可以dp了然后我又不会了

然后看了一下好像就只会dfs判0环

然后就看题解了。。

dp[u][k] 表示到u比最短路小于等于k的总方案数

那么到n所有小于等于k的方案数都应该计入答案,dp[n][k] = 1

考虑走一步对于答案的影响

首相原来在u这个点,到最短路的距离是dis[u]

然后走到v这个点后,那么当前方案对于u来说走的最短距离就是f[i] + dis[v]

那么对于这个方案,最短距离是dis[u],当前方案是dis[v] + f[i],多出来的就是 dis[v] + f[i] - dis[u]

每次对于当前的kk来说去找v的kk - dis[v] - f[i] + dis[u],用记忆化搜索就可以解决了

然后对于0环的情况,就是一个kk绕了一圈又回到这里计算答案,记录一下这个地方是否有过这个状态在递归栈中,如果有就拜拜了

贴上代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstring>
#include<queue>

using namespace std;

int read() {
	int rt = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		rt = (rt << 1) + (rt << 3) + ch - '0';
		ch = getchar();
	}
	return rt * f;
}

struct Node {
	int val, id;
	bool operator < (const Node &rhs) const {
		return val > rhs.val;
	}
};

priority_queue<Node> q;

const int maxn = 1e5 + 5;

const int maxm = 2e5 + 5;

int h1[maxn], h2[maxn], tot1, tot2, dis[maxn], dp[maxn][55];

bool ins[maxn][55];

int n, m, k, p, T;

struct Edge {
	int nxt, tov, f;
}e1[maxm], e2[maxm];

void init() {
	tot1 = tot2 = 0;
	memset(h1, 0, sizeof(h1));
	memset(h2, 0, sizeof(h2));
	memset(dp, 0, sizeof(dp));
	memset(ins, 0, sizeof(ins));
}

void add1(int u, int v, int w) {
	tot1++;
	e1[tot1].tov = v;
	e1[tot1].nxt = h1[u];
	e1[tot1].f = w;
	h1[u] = tot1;
}

void add2(int u, int v, int w) {
	tot2++;
	e2[tot2].tov = v;
	e2[tot2].nxt = h2[u];
	e2[tot2].f = w;
	h2[u] = tot2;
}

void di() {
	while (!q.empty()) q.pop();
	for (int i = 1; i <= n; i++) dis[i] = 1e9;
	dis[n] = 0;
	q.push((Node){0, n});
	while (!q.empty()) {
		Node u = q.top();
		q.pop();
		if (u.val != dis[u.id]) continue;
		for (int i = h2[u.id]; i; i = e2[i].nxt) {
			int v = e2[i].tov;
			if (dis[v] > dis[u.id] + e2[i].f) {
				dis[v] = dis[u.id] + e2[i].f;
				q.push((Node){dis[v], v});
			}
		}
	}
}

int dfs(int u, int kk) {
	if (ins[u][kk]) return dp[u][kk] = -1;
	if (dp[u][kk]) return dp[u][kk];
	ins[u][kk] = 1;
	if (u == n) dp[u][kk] = 1;
	else dp[u][kk] = 0;
	for (int i = h1[u]; i; i = e1[i].nxt) {
		int v = e1[i].tov;
		if (kk >= dis[v] + e1[i].f - dis[u]) {
			int tmp = dfs(v, kk - dis[v] - e1[i].f + dis[u]);
			if (tmp == -1) return dp[u][kk] = -1;
			else dp[u][kk] += tmp, dp[u][kk] %= p;
		}
	}
	ins[u][kk] = 0;
	return dp[u][kk];
} 

int main() {
	T = read();
	while (T--) {
		init();
		n = read(), m = read(), k = read(), p = read();
		for (int i = 1; i <= m; i++) {
			int u, v, w;
			u = read(), v = read(), w = read();
			add1(u, v, w);
			add2(v, u, w);
		}
		di();
		int ans = dfs(1, k);
		printf("%d\n", ans);
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值