洛谷刷题笔记——P3953 [NOIP2017 提高组] 逛公园

参考资料:

  • 洛谷_风休住大佬的题解

[NOIP2017 提高组] 逛公园

题目描述

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

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

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

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

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

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

输入格式

第一行包含一个整数 T T T, 代表数据组数。

接下来 T T T 组数据,对于每组数据: 第一行包含四个整数 N , M , K , P N,M,K,P N,M,K,P,每两个整数之间用一个空格隔开。

接下来 M M M 行,每行三个整数 a i , b i , c i a_i,b_i,c_i ai,bi,ci,代表编号为 a i , b i a_i,b_i ai,bi 的点之间有一条权值为 c i c_i ci 的有向边,每两个整数之间用一个空格隔开。

输出格式

输出文件包含 T T T 行,每行一个整数代表答案。

样例 #1

样例输入 #1

2
5 7 2 10
1 2 1
2 4 0
4 5 2
2 3 2
3 4 1
3 5 2
1 5 3
2 2 0 10
1 2 0
2 1 0

样例输出 #1

3
-1

提示

【样例解释1】

对于第一组数据,最短路为 3 3 3 1 → 5 , 1 → 2 → 4 → 5 , 1 → 2 → 3 → 5 1\to 5, 1\to 2\to 4\to 5, 1\to 2\to 3\to 5 15,1245,1235 3 3 3 条合法路径。

【测试数据与约定】

N ≤ 1 0 5 N \leq 10^5 N105 M ≤ 2 × 1 0 5 M \leq 2\times10^5 M2×105 K ≤ 50 K \leq 50 K50

思路

最短路+dp

本题需要建正、反两张图,正图用于求最短路,反图用于记忆化搜索和dp。

由于本题目中约定每条边的权值 ≥ 0 \geq 0 0,所以图中不存在负环,可以在正图上运行迪杰斯特拉算法求出起点到所有结点的最短距离,保存在数组d[]中,其中,d[i]表示起点到结点i的最短距离。

然后就到了本题的精髓之处:动态规划。定义状态dp[i][k]表示:从起点到结点i的长度恰好为d[i]+k的路径的条数。显然,问题所求的答案为:dp[n][0] + dp[n][1] + ... + dp[n][K]。然后考虑状态之间的转移。假设在正图中有一条有向边 ( v , u ) (v,u) (v,u),其权值为 w w w,则我们可以通过状态dp[v][x]转移到状态dp[u][k]。对于结点u, v有如下关系:
d [ v ] + x + w = d [ u ] + k d[v]+x+w=d[u]+k d[v]+x+w=d[u]+k
于是有:
x = d [ u ] − d [ v ] + k − w x=d[u]-d[v]+k-w x=d[u]d[v]+kw
由此,我们得到状态转移方程:
d p [ u ] [ k ] = ∑ ( v , u ) ∈ G 1 d p [ v ] [ d [ v ] − d [ u ] + k − w ] dp[u][k]=\sum_{(v,u)\in G_1}dp[v][d[v]-d[u]+k-w] dp[u][k]=(v,u)G1dp[v][d[v]d[u]+kw]

反图上进行记忆化搜索即可实现动态规划。

然后我们考虑输出为-1的情况:分析题目可知,只有当图中存在零环,且该零环中某一结点出现在某条合法路径上时,最终输出才为-1。我们可以在记忆化搜索的过程中找出零环。假设当前要求dp[i][k],我们沿着反图进行记忆化搜索,在得到最终的dp[i][k]之前,我们如果回到了dp[i][k],说明结点i在一个零环上。

我们还需要考虑动态规划的边界条件。首先,对于任意一个点i,当 k < 0 k<0 k<0时,dp[i][k]一定为0。再者,我们在进行真正的记忆化搜索前,需要先记忆化搜索dp[1][0],如果在搜索的过程中发现零环,则直接输出-1即可,否则令dp[1][0]1

完整代码

#include
#define maxn 200005
using namespace std;

int T;
int n, m, K, p;
int cnt1 = 0, cnt2 = 0;
int head1[maxn], head2[maxn];
int d[maxn];
bool vis[maxn];
int dp[maxn][55];
bool ins[maxn][55];
bool infty;
int ans;

struct EDGE{
	int to;
	int v;
	int next;
}edge1[maxn], edge2[maxn];

void addEdge1(int fr, int to, int v){
	cnt1++;
	edge1[cnt1].to = to;
	edge1[cnt1].v = v;
	edge1[cnt1].next = head1[fr];
	head1[fr] = cnt1;
}

void addEdge2(int fr, int to, int v){
	cnt2++;
	edge2[cnt2].to = to;
	edge2[cnt2].v = v;
	edge2[cnt2].next = head2[fr];
	head2[fr] = cnt2;
}

struct cmp{
	bool operator()(pair<int, int>p1, pair<int, int>p2){
		return p1.second>p2.second;
	}
};

void dij(int i){
	memset(d, 37, sizeof(d));
	priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> q;
	q.push(make_pair(i, 0));
	d[i] = 0;
	while(!q.empty()){
		pair<int, int> p = q.top();	
		q.pop();
		int pos = p.first;
		if(vis[pos]) continue;
		vis[pos] = 1;
		for(int i=head1[pos];i!=0;i=edge1[i].next){
			int to = edge1[i].to, v = edge1[i].v;
			if(vis[to]) continue;
			if(d[to]>d[pos]+v){
				d[to] = d[pos]+v;
				q.push(make_pair(to, d[to]));
			}
		}
	}
}

void dfs(int u, int k){
	if(infty) return;
	if(k<0) return;
	if(ins[u][k]){
		infty = true;
		return;
	}
	if(dp[u][k]!=0) return;
	ins[u][k] = true;
	int res = 0;
	for(int i=head2[u];i!=0;i=edge2[i].next){
		int to = edge2[i].to, v = edge2[i].v;
		int x = d[u]-d[to]+k-v;
		dfs(to, x);
		if(x>=0) res += dp[to][x];
		res %= p;
	}
	dp[u][k] = res;
	ins[u][k] = false;
}

void init(){
	cnt1 = cnt2 = 0;
	memset(head1, 0, sizeof(head1));
	memset(head2, 0, sizeof(head2));
	memset(edge1, 0, sizeof(edge1));
	memset(edge2, 0, sizeof(edge2));
	memset(d, 0, sizeof(d));
	memset(vis, 0, sizeof(vis));
	memset(ins, 0, sizeof(ins));
	memset(dp, 0, sizeof(dp));
	infty = false;
	ans = 0;
}

int main(){
	cin>>T;
	while(T--){
		init();
		cin>>n>>m>>K>>p;
		for(int i=0;i<m;i++){
			int a, b, c;
			cin>>a>>b>>c;
			addEdge1(a, b, c);
			addEdge2(b, a, c);
		}
		dij(1);
		dfs(1, 0);
		dp[1][0] = 1;
		if(infty){
			cout<<-1<<'\n';
			continue;
		}
		for(int i=0;i<=K;i++){
			dfs(n, i);
			if(infty) break;
			ans += dp[n][i];
			ans %= p;
		}
		if(infty) cout<<-1<<'\n';
		else cout<<ans<<'\n'; 
	}
	return 0;
}

你可能感兴趣的:(洛谷刷题笔记——P3953 [NOIP2017 提高组] 逛公园)