[NOIP2017 提高组] 逛公园 题解

题目

  • \(\text{30pts}\)

显然就是这道题

  • \(\text{100pts}\)

肯定要跑最短路的。令 \(d_i\) 表示 \(i\) 到 \(n\) 的最短路长度。
\(f_{u,i}\) 表示从 \(u\) 到 \(n\) 长度为 \(d_u+i\) 的路径个数。

\(dis\) 数组显然可以建反图然后跑源点为 \(n\) 的最短路。

考虑 \(f\) 数组的转移。

\[f_{u,i}=\sum{f_{v,i-(d_v+w-d_u)}} \]

其中,存在 \(u\) 到 \(v\) 的边且边权为 \(w\)。
可以这样做的原因就是 \(d_v+w-d_u\) 就是给 \(v\) 给 \(u\) 的路径距离最短路“偏差”的贡献,这样的话 \(v\) 中的偏差就必须为 \(i-(d_v+w-d_u)\)。

这样可以直接记忆化搜索也可以就跑一遍拓扑排序,我是用记搜写的。
有无数种的路线的情况就是有边权均为 \(0\) 的环,在记搜里面记录个 \(exi_{u,i}\) 数组即可,如果此时数组值为真的话就相当于找到了边权为 \(0\) 的环。

显然时间复杂度 \(O(m\log m+nk)\)。

代码:

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

using namespace std;

using LL = long long;

const int INF = 0x3f3f3f3f;
const int N = 1e5 + 5;

struct Edge {
  int to, w;
  
  Edge () {}
  Edge (int _to, int _w) { to = _to, w = _w; }
  ~Edge () {}
};

struct Node {
  int id, dis;
  
  Node () {}
  Node (int _id, int _dis) { id = _id, dis = _dis; }
  ~Node () {}
  
  friend bool operator < (const Node& n1, const Node& n2) {
    return n1.dis > n2.dis;
  }
};

vector <Edge> G[N], rG[N];
priority_queue <Node> q;

int T, n, m, k, p;
int u, v, w;
LL f[N][55], dis[N];
bool flag, vis[N], exi[N][55];

int add (int x, int y) {
  return (x + y >= p) ? (x + y - p) : (x + y);
}

void init () {
  flag = false;
  for (int i = 1; i <= n; ++i) {
    G[i].clear(), rG[i].clear();
    for (int j = 0; j <= k; ++j)
      f[i][j] = -1;
  }
  while (!q.empty())
    q.pop();
}

void AddEdge (int u, int v, int w) {
  G[u].push_back(Edge(v, w));
  rG[v].push_back(Edge(u, w));
}

void Dijkstra (int s) {
  fill(dis + 1, dis + 1 + n, INF), 
  fill(vis + 1, vis + 1 + n, false);
  q.push(Node(s, 0)), dis[s] = 0;
  while (!q.empty()) {
    Node nowtop = q.top(); q.pop();
    int nowid = nowtop.id;
    if (vis[nowid]) continue ;
    vis[nowid] = true;
    for (auto Ed : rG[nowid]) {
      int to = Ed.to, w = Ed.w;
      if (dis[to] > dis[nowid] + w) {
        dis[to] = dis[nowid] + w;
        q.push(Node(to, dis[to]));
      }
    }
  }
}

LL Solve (int x, int y) {
  if (exi[x][y]) {
    flag = true;
    return 0;
  }
  if (f[x][y] > 0)
    return f[x][y];
//  f[x][y] = 0;
  exi[x][y] = true;
  LL ret = 0;
  for (auto Ed : G[x]) {
    int to = Ed.to, w = Ed.w;
    int temp = y - (dis[to] + w - dis[x]);
    if (temp < 0 || temp > k) continue ;
    ret = add(ret, Solve(to, temp));
    if (flag) return 0;
  }
  if (x == n && !y)
    ret = 1;
  exi[x][y] = false;
  return f[x][y] = ret;
}

int main() {
  scanf("%d", &T);
  while (T--) {
    init();
    scanf("%d%d%d%d", &n, &m, &k, &p);
    for (int i = 1; i <= m; ++i) {
      scanf("%d%d%d", &u, &v, &w);
      AddEdge(u, v, w);
    }
    Dijkstra(n);
    LL res = 0;
    for (int i = 0; i <= k; ++i) {
      for (int j = 1; j <= n; ++j)
        for (int s = 0; s <= k; ++s)
          exi[j][s] = false;
      res = add(res, Solve(1, i));
    }
    printf("%lld\n", flag ? -1 : res);
  }
  return 0;
}

貌似不开 O2 过不了(

上一篇:get方式提交中文乱码解决


下一篇:洛谷 P3957 [NOIP2017 普及组] 跳房子(二分,单调队列优化dp)