Luogu P3953【NOIP2017】逛公园【最短路+拓扑排序+动态规划】

题目描述

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

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

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

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

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

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

输入输出格式

输入格式:

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

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

接下来MM行,每行三个整数a_i,b_i,c_iai?,bi?,ci?,代表编号为a_i,b_iai?,bi?的点之间有一条权值为 c_ici?的有向边,每两个整数之间用一个空格隔开。

输出格式:

输出文件包含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 条合法路径。

【测试数据与约定】

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

测试点编号   TT    NN    MM    KK    是否有0边
1 5 5 10 0
2 5 1000 2000 0
3 5 1000 2000 50
4 5 1000 2000 50
5 5 1000 2000 50
6 5 1000 2000 50
7 5 100000 200000 0
8 3 100000 200000 50
9 3 100000 200000 50
10 3 100000 200000 50

对于 100%的数据, 1 <= P <= 10^9,1 <= a_i,b_i <= N ,0 <= c_i <= 1000, 1≤P≤109,1≤ai?,bi?≤N , 0≤ci?≤1000。

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

Solution :

首先从起点和终点的最短路一定是要跑的。

注意到k不大于50, nk的动态规划是可以考虑的。

设起点到第i个点最短路为dis[i] , 这样dp[i][j]表示到第i个点长度为dis[i]+j的路径条数。

考虑到可以同一层转移,显然这种转移边满足dis[x] + len == dis[y]。

那么我么拓扑排序一下确定转移顺序即可。

考虑-1的情况,若有零环且存在通过零环并且长度<=dis[n]+k的路径则答案无限大。

namespace可好写了,写得可长了。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define travel(x, i) for (int i = fir[x]; i; i = e[i].nxt)
using namespace std;

namespace fastIO {
#define buf_size 1000000
  bool error;
  inline char gc() {
    static char buf[buf_size + 1], *l = buf, *r = buf;
    if (l == r) {
      l = buf;
      r = buf + fread(buf, 1, buf_size, stdin);
      if (l == r) {error = 1; return -1;}
    }
    return *l ++;
  }
  inline bool isnum(char ch) {
    return (‘0‘ <= ch && ch <= ‘9‘) || ch == ‘-‘;
  }
  inline void read(int &x) {
    char ch; x = 0;
    while (!isnum(ch = gc()) && !error);
    if (error) return;
    do {x = (x << 1) + (x << 3) + ch - ‘0‘;} while (isdigit(ch = gc()) && !error);
  }
#undef buf_size
}
using namespace fastIO;

typedef pair <int, int> pii;
const int N = 1e5 + 5;
const int M = 2e5 + 5;

int n, m, k, mod;
int ans[N][52];

namespace Graph1 {
  struct edge {
    int nxt, to, len;
  } e[M];
  int fir[N], dis[N], cnt;
  bool in[N];

  inline void clear() {memset(fir, 0, sizeof fir); cnt = 0;}
  inline void add(int x, int y, int l) {
    e[++ cnt] = (edge){fir[x], y, l};
    fir[x] = cnt;
  }
  inline void dijkstra() {
    memset(dis, -1, sizeof dis);
    memset(in, 0, sizeof in);
    priority_queue <pii, vector <pii>, greater <pii> > Q;
    Q.push(make_pair(0, 1));
    dis[1] = 0;
    pii cur;
    int x;
    while (!Q.empty()) {
      cur = Q.top(); Q.pop();
      x = cur.second;
      if (in[x]) continue;
      in[x] = 1;
      travel(x, i)
        if (!in[e[i].to] && (dis[x] + e[i].len < dis[e[i].to] || dis[e[i].to] == -1)) {
          dis[e[i].to] = dis[x] + e[i].len;
          Q.push(make_pair(dis[e[i].to], e[i].to));
        }
    }
  }
}

namespace Graph2 {
  struct edge {
    int nxt, to, len;
  } e[M];
  int fir[N], dis[N], cnt;
  bool in[N];

  inline void clear() {memset(fir, 0, sizeof fir); cnt = 0;}
  inline void add(int x, int y, int l) {
    e[++ cnt] = (edge){fir[x], y, l};
    fir[x] = cnt;
  }
  inline void dijkstra() {
    memset(dis, -1, sizeof dis);
    memset(in, 0, sizeof in);
    priority_queue <pii, vector <pii>, greater <pii> > Q;
    Q.push(make_pair(0, n));
    dis[n] = 0;
    pii cur;
    int x;
    while (!Q.empty()) {
      cur = Q.top(); Q.pop();
      x = cur.second;
      if (in[x]) continue;
      in[x] = 1;
      travel(x, i)
        if (!in[e[i].to] && (dis[x] + e[i].len < dis[e[i].to] || dis[e[i].to] == -1)) {
          dis[e[i].to] = dis[x] + e[i].len;
          Q.push(make_pair(dis[e[i].to], e[i].to));
        }
    }
  }
}

namespace Graph3 {
  struct edge {
    int nxt, to;
  } e[M];
  int fir[N], cnt = 0, tot;
  int ord[N], deg[N];

  inline void clear() {
    memset(fir, 0, sizeof fir);
    memset(deg, 0, sizeof deg);
    cnt = 0;
  }
  inline void add(int x, int y) {
    e[++ cnt] = (edge){fir[x], y};
    fir[x] = cnt;
    deg[y] ++;
  }
  inline void topo() {
    queue <int> Q;
    for (int i = 1; i <= n; i ++)
      if (!deg[i]) Q.push(i);
    int x; tot = 0;
    while (!Q.empty()) {
      x = Q.front(); Q.pop();
      ord[++ tot] = x;
      travel(x, i) {
        deg[e[i].to] --;
        if (!deg[e[i].to]) Q.push(e[i].to);
      }
    }
  }
}

namespace Graph4 {
  struct edge {
    int nxt, to;
  } e[M];
  int fir[N], deg[N], cnt;

  inline void clear() {
    memset(fir, 0, sizeof fir);
    memset(deg, 0, sizeof deg);
    cnt = 0;
  }
  inline void add(int x, int y) {
    e[++ cnt] = (edge){fir[x], y};
    fir[x] = cnt;
    deg[y] ++;
  }
  inline bool topo() {
    queue <int> Q;
    for (int i = 1; i <= n; i ++)
      if (!deg[i]) Q.push(i);
    int x;
    while (!Q.empty()) {
      x = Q.front(); Q.pop();
      travel(x, i) {
        deg[e[i].to] --;
        if (!deg[e[i].to]) Q.push(e[i].to);
      }
    }
    for (int i = 1; i <= n; i ++)
      if (deg[i] && Graph1 :: dis[i] + Graph2 :: dis[i] <= Graph1 :: dis[n] + k) return 0;
    return 1;
  }
}

int x[M], y[M], l[M];

inline void modadd(int &x, int y) {
  if ((x += y) >= mod) x -= mod;
}

using namespace Graph1;

inline void solve(int T) {
  clear();
  Graph2 :: clear();
  Graph3 :: clear();
  Graph4 :: clear();
  read(n); read(m); read(k); read(mod);
  for (int i = 1; i <= m; i ++) {
    read(x[i]); read(y[i]); read(l[i]);
    add(x[i], y[i], l[i]);
    Graph2 :: add(y[i], x[i], l[i]);
  }
  dijkstra();
  Graph2 :: dijkstra();
  for (int i = 1; i <= m; i ++) {
    if (dis[x[i]] == -1 || Graph2 :: dis[y[i]] == -1) continue;
    if (dis[x[i]] + l[i] == dis[y[i]]) Graph3 :: add(x[i], y[i]);
    if (l[i] == 0) Graph4 :: add(x[i], y[i]);
  }
  if (Graph4 :: topo() == 0) return (void)puts("-1");
  Graph3 :: topo();
  memset(ans, 0, sizeof ans);
  ans[1][0] = 1;
  int w, xx, g = Graph3 :: tot;
  for (int i = 0; i <= k; i ++) {
    for (int j = 1; j <= g; j ++) {
      xx = Graph3 :: ord[j];
      if (dis[xx] == -1) continue;
      travel(xx, p) {
        w = dis[xx] + i + e[p].len - dis[e[p].to];
        if (0 <= w && w <= k) modadd(ans[e[p].to][w], ans[xx][i]);
      }
    }
  }
  int Ans = 0;
  for (int i = 0; i <= k; i ++) modadd(Ans, ans[n][i]);
  printf("%d\n", Ans);
}

int main() {
  int T;
  read(T);
  while (T --) solve(T);
  return 0;
}

  

时间: 2024-10-07 06:47:10

Luogu P3953【NOIP2017】逛公园【最短路+拓扑排序+动态规划】的相关文章

[Luogu P3953] 逛公园 (最短路+拓扑排序+DP)

题面 传送门:https://www.luogu.org/problemnew/show/P3953 Solution 这是一道神题 首先,我们不妨想一下K=0,即求最短路方案数的部分分. 我们很容易可以想到一个做法,就是魔改迪杰斯特拉做法: 如果一个点可以更新到达其他点的距离,那个点的方案数就是这个点的方案数:如果一个点所更新出来的距离和之前的相等,那个点的方案数加等当前点的方案数. 用式子可以表现为: f[j]=f[i] (dis[j]>dis[i]+x)   f[j]+=f[i] (dis

【比赛】NOIP2017 逛公园

考试的时候灵光一闪,瞬间推出DP方程,但是不知道怎么判-1,然后?然后就炸了. 后来发现,我只要把拓扑和DP分开,中间加一个判断,就AC了,可惜. 看这道题,我们首先来想有哪些情况是-1:只要有零环在满足题目要求的路径上,那么这条路径就可以不停地走,于是就-1了. 如何判有没有零环呢? 机械化地两遍不同方向的SPFA,就知道某个点在不在最短路上,以此建一个最短路图,在最短路图上找零环.于是就拓扑啦.稍加判断就解决了整个题目最关键的-1. 接下来就是DP了,设f[i][j]表示走到i点,走过路程已

[NOIP2017] 逛公园

[NOIP2017] 逛公园 题目大意: 给定一张图,询问长度 不超过1到n的最短路长度加k 的1到n的路径 有多少条. 数据范围: 点数\(n \le 10^5\) ,边数\(m \le 2*10^5\) 题目解法 两个月后再看也不是太难,自己就能独立思考出来. 首先是判-1的问题,显然能产生-1的只有0环. 所以把0环都找出来, 然后检查一下\(dis[\)\(1\),环\(]\) + \(dis[\)环,\(n]\) 是否小于等于 \(dis[1,n]+K\)即可. 如果不是无限路径的话,

Luogu P3953 逛公园(最短路+记忆化搜索)

P3953 逛公园 题面 题目描述 策策同学特别喜欢逛公园.公园可以看成一张 \(N\) 个点 \(M\) 条边构成的有向图,且没有自环和重边.其中 \(1\) 号点是公园的入口,\(N\) 号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间. 策策每天都会去逛公园,他总是从 \(1\) 号点进去,从 \(N\) 号点出来. 策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间.如果 \(

NOIP2017 Day1 T3 逛公园(最短路+拓扑排序+DP)

神tm比赛时多清个零就有60了T T 首先跑出1起点和n起点的最短路,因为k只有50,所以可以DP.设f[i][j]表示比最短路多走i的长度,到j的方案数. 我们发现如果在最短路上的和零边会有后向性,怎么办呢?拓扑排序. 把最短路上的点和零边的点拉出来跑拓扑排序,如果有零环的话必定度数不为0,而且要注意零环必须在<=最短路+k的路径上才输出-1,这个就用刚刚跑出来的1起点到n起点的最短路来判断就好了. 然后先按拓扑序DP出i相同的,然后再DP不在最短路上或者零边的. #include<iost

20151230训练题解(最短路+拓扑排序)

http://acm.hust.edu.cn/vjudge/contest/view.action?cid=103223#problem/B 这道题用经典的dijsktra算法,大概思路就是用dist[x]存储x到已经确定集合的最短路,n次循环到这个这个最小值,然后更新其他点到新集合的最短路即对应的dist[] 1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <al

BZOJ1880 SDOI2009 Elaxia的路线 最短路+拓扑排序

题意:给定两个点对和一张无向图,求两个点对的最短路中,重边边权和的最大值 题解: 首先从给出的四个点出发跑出到其他所有点的最短路,然后判断哪些边是重边.找出所有重边后,将其构有向图,在该图上用拓扑排序求最长路. 开始的时候枚举每一条边我没有建反向边,而是每次判定的时候互换一下边的始末点看是否合法,结果最后一个点死活过不去.后来上网搜题解才知道,必须建反向边,与原边分别判断.果然偷懒就是不行啊…… #include <cstdio> #include <cstring> #inclu

1880. [SDOI2009]Elaxia的路线【最短路+拓扑排序】

Description 最近,Elaxia和w**的关系特别好,他们很想整天在一起,但是大学的学习太紧张了,他们 必须合理地安排两个人在一起的时间.Elaxia和w**每天都要奔波于宿舍和实验室之间,他们 希望在节约时间的前提下,一起走的时间尽可能的长. 现在已知的是Elaxia和w**所在的宿舍和实验室的编号以及学校的地图:地图上有N个路 口,M条路,经过每条路都需要一定的时间. 具体地说,就是要求无向图中,两对点间最短路的最长公共路径. Input 第一行:两个整数N和M(含义如题目描述).

【CSP模拟赛】益智游戏(最短路&amp;拓扑排序)

题目描述 小P和小R在玩一款益智游戏.游戏在一个正权有向图上进行. 小P 控制的角色要从A 点走最短路到B 点,小R 控制的角色要从C 点走最短路到D 点. 一个玩家每回合可以有两种选择,移动到一个相邻节点或者休息一回合. 假如在某一时刻,小P 和小R 在相同的节点上,那么可以得到一次特殊奖励,但是在每 个节点上最多只能得到一次. 求最多能获得多少次特殊奖励 输入格式 第一行两个整数n,m 表示有向图的点数和边数. 接下来m 行每行三个整数xi,yi,li,表示从xi 到yi 有一条长度为li