• 「NOIP 2017」逛公园


    逛公园

    题目描述

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

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

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

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

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

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

    输入格式

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

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

    接下来 (M) 行,每行三个整数 (a_i,b_i,c_i)​,代表编号为 (a_i,b_i) ​的点之间有一条权值为 (c_i) ​的有向边,每两个整数之间用一个空格隔开。

    输出格式

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

    输入输出样例

    输入 #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)(1-5,1-2-4-5,1-2-3-5)(3) 条合法路径。

    【测试数据与约定】

    对于不同的测试点,我们约定各种参数的规模不会超过如下

    对于 (100\%) 的数据, (1 le P le 10^9,1 le a_i,b_i le N ,0 le c_i le 1000)

    数据保证:至少存在一条合法的路线。

    思路

    刚开始的求最短路不用说,(SPFA)(dijkstra) 都可,不会有人真的以为这道题会卡 (SPFA) 吧,不会吧,不会吧。

    有一说一,前者确实慢,但我就是喜欢

    先讨论一下 (-1) 的情况,为什么会出现无穷条路径?

    易证,当我们有 (0) 环的时候,我们就会在上图中的 (1-6-5) 这个环里一直走啊走啊走~~~

    如果这个环的权值不为 (0),是不是还是 (-1) 呢,显然不是的。

    因为在这个环里走的话,你走的路径会越来越长,长度总有一刻会超过限制,所以路径是有限的。


    处理完了特殊情况,路径数量怎么求呢?

    如果你直接暴搜,恭喜您,您已经取得了 (30opts) 的好成绩,不过,您会 (T) 到飞起。

    正解是什么呢?

    首先,我们会发现,(k) 的数据范围很小,我们可以从他入手。

    直接暴搜???不,我们要有记忆的暴搜。

    先建一个反图,反着跑到 (1)

    我们定义一个 (f[i][j]),表示我们跑到了 (i) 点,比最短路 (d)(j) 的长度。

    这样我们枚举 (j),疯狂跑,当 (i=1) 并且 (j=0) 时,就是一个答案了。

    在判断 (0) 环时,我们就判断一下当前 (f[i][j]) 有没有跑过,如果跑过,直接返回 (-1) 即可。

    代码

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    
    #define int long long
    
    using namespace std;
    
    const int maxn = 2e5 + 50, INF = 0x3f3f3f3f;
    
    inline int read(){
    	int x = 0, w = 1;
    	char ch;
    	for (; ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
    	for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
    	return x * w;
    }
    
    int T;
    int n, m, k, mod, ans;
    
    struct Edge{
    	int to, next, w;
    }e[maxn], g[maxn];
    
    int tot1, head1[maxn];
    
    void Add1(int u, int v, int w){
    	e[++tot1].to = v;
    	e[tot1].w = w;
    	e[tot1].next = head1[u];
    	head1[u] = tot1;
    }
    
    int tot2, head2[maxn];
    
    void Add2(int u, int v, int w){
    	g[++tot2].to = v;
    	g[tot2].w = w;
    	g[tot2].next = head2[u];
    	head2[u] = tot2;
    }
    
    int dis[maxn];
    bool vis[maxn];
    
    void SPFA(int x){//你想跑什么都行,开心就好
    	memset (dis, 0x3f, sizeof dis);
    	memset (vis, 0, sizeof vis);
    	dis[x] = 0;
    	queue <int> q;
    	q.push(x);
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (register int i = head1[u]; i; i = e[i].next) {
    			int v = e[i].to;
    			if (dis[v] > dis[u] + e[i].w) {
    				dis[v] = dis[u] + e[i].w;
    				if (!vis[v]) {
    					vis[v] = 1;
    					q.push(v);
    				}
    			}			
    		}
    	}
    }
    
    bool flag[maxn][60];
    int dp[maxn][60];
    
    int DFS(int u, int w){
    	if (w < 0 || w > k) return 0;
    	if (flag[u][w]) return -1;//跑过这个状态了,返回-1
    	if (dp[u][w] != -1) return dp[u][w];//如果有记忆化过的,直接返回即可
    	flag[u][w] = 1;
    	int sum = 0;
    	for (register int i = head2[u]; i; i = g[i].next) {
    		int v = g[i].to;
    		int tmp = DFS(v, dis[u] + w - dis[v] - g[i].w);//往前跑
    		if (tmp == -1) return -1;
    		sum = (sum + tmp) % mod;
    	}
    	if (u == 1 && w == 0) {
    		sum ++;
    	}
    	dp[u][w] = sum;
    	flag[u][w] = 0;
    	return sum;
    }
    signed main(){
    	T = read();
    	while (T --) {
    		memset (vis, 0, sizeof vis);//多组数据清空!!!
    		memset (dp, -1, sizeof dp);//初始为-1,因为有0的情况
    		memset (flag, 0, sizeof flag);
    		memset (head1, 0, sizeof head1);
    		memset (head2, 0, sizeof head2);
    		memset (e, 0, sizeof e);
    		ans = 0, tot1 = 0, tot2 = 0;
    		n = read(), m = read(), k = read(), mod = read();
    		for (register int i = 1; i <= m; i ++) {
    			int u = read(), v = read(), w = read();
    			Add1(u, v, w);
    			Add2(v, u, w);
    		}
    		SPFA(1);
    		bool pd = 0;
    		for (register int i = 0; i <= k; i ++) {//枚举k
    			int now = DFS(n, i);
    			if (now == -1) {
    				puts("-1");
    				pd = 1;
    				break;
    			}else {
    				ans = (ans + now) % mod;
    			}
    		}
    		if (pd == 0) {
    			printf("%lld
    ", ans);
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    浅析Scrapy框架运行的基本流程
    排序和搜索
    设计模式:桥接模式及代码示例、桥接模式在jdbc中的体现、注意事项
    设计模式:适配器模式(类适配器、对象适配器、接口适配器)
    设计模式:建造者模式及在jdk中的体现,建造者模式和工厂模式区别
    java的线程、创建线程的 3 种方式、静态代理模式、Lambda表达式简化线程
    设计模式:原型模式介绍 && 原型模式的深拷贝问题
    设计模式:工厂设计模式介绍及3种写法(简单工厂、工厂方法、抽象工厂)
    设计模式:单例模式介绍及8种写法(饿汉式、懒汉式、Double-Check、静态内部类、枚举)
    设计模式七大原则及代码示例
  • 原文地址:https://www.cnblogs.com/Rubyonly233/p/13507456.html
Copyright © 2020-2023  润新知