[NOIP2017 d2t3] 逛公园

Problem

给一张有向图(无重边、自环,如果有的话应该会麻烦许多吧)。求从 1 1 ~n有多少种走的方案?其中每一种方案中,走的距离均不超过最短路+K。

+=NOIP2017 多 组 测 试 数 据 + 此 题 数 据 范 围 = N O I P 2017 卡 常 神 题

Solution

提供一种优美的做法——记忆化搜索。
建一个正图、一个反图。跑一次反向最短路(常用技巧)。
f[u][k] f [ u ] [ k ] 表示 dis(u,n)MinDis(u,n)+K d i s ( u , n ) ≤ M i n D i s ( u , n ) + K 的方案数,那么答案就是 f[1][K] f [ 1 ] [ K ]
先列出每次的转移方程。
f[u][k]=f[v][k(MinDis(v,n)MinDis(u,n)+val(u>v))] f [ u ] [ k ] = ∑ f [ v ] [ k − ( M i n D i s ( v , n ) − M i n D i s ( u , n ) + v a l ( u − > v ) ) ]
那如何判0环呢?
每次dfs的时候,如果还没做完,在它的转移中又搜到它自己了,即是无解。可以开一个 instack i n s t a c k 数组来记录一下。
dijkstra d i j k s t r a 用线段树维护最小值,不要用 priority p r i o r i t y 的那个堆,太慢。
甚至我用了常数更小的 zkw z k w

#include <cstdio>
#include <cstring>
#define N 100010
#define M 200010
#define re register int
#define fp(i, a, b) for(re i = a, I = b; i <= I; ++i)
struct edge {int to, val, next;}e[M<<1];
int T, n, m, K, P, f[N][51], cnt = 1;
int dis[N], head1[N], headn[N];
bool in[N][51];
inline char gc() {
    static char now[1<<16], *S, *T;
    if(S == T) {T = (S = now) + fread(now, 1, 1<<16, stdin); if(S == T) return EOF;}
    return *S++;
}
inline int read() {
    int x = 0, f = 1; char c = gc();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = gc();}
    while(c >= '0' && c <= '9') {x = x * 10 + c - 48; c = gc();}
    return x * f;
}
namespace zkw {
    int t[N<<2], sgt;
    inline void Set(re n) {sgt = 1; while(sgt <= n) sgt<<= 1; --sgt; t[0] = N - 1;}
    inline void clr() {fp(i, 1, (sgt<<1) + 1) t[i] = 0;}
    inline int cmp(const re &a, const re &b) {return dis[a] < dis[b] ? a : b;}
    inline void mdy(re x, re w) {for(re i = x + sgt; dis[t[i]] > w; i>>= 1) t[i] = x; dis[x] = w;}
    inline void del(re x) {t[x+= sgt] = 0; x>>= 1; while(x) t[x] = cmp(t[x<<1], t[x<<1|1]), x>>= 1;}
}
using namespace zkw;
inline void dij() {
    memset(dis, 9, 4 * (n + 1)); clr(); mdy(n, 0);
    fp(j, 1, n) {
        re u = t[1]; del(u);
        for(re i = headn[u]; i; i = e[i].next)
            if(dis[e[i].to] > dis[u] + e[i].val) mdy(e[i].to, dis[u] + e[i].val);
    }
}
inline void add(re u, re v, re w, re *fi) {e[++cnt] = (edge){v, w, fi[u]}; fi[u] = cnt;}
inline void mod(re &a) {a>= P ? a-= P : 0;}
int dfs(re u, re k) {
    if(in[u][k]) return -1;
    if(f[u][k]) return f[u][k];
    in[u][k] = 1; u == n ? f[u][k] = 1 : 0;
    for(re i = head1[u], temp; i; i = e[i].next) {
        if((temp = dis[e[i].to] - dis[u] + e[i].val) <= k) {
            int w;
            if((w = dfs(e[i].to, k - temp)) == -1) return f[u][k] = -1;
            mod(f[u][k]+= w);
        }
    }
    return in[u][k] = 0, f[u][k];
}
int main() {
    T = read();
    while(T--) {
        cnt = 1;
        memset(f, 0, sizeof(f)); memset(in, 0, sizeof(in));
        n = read(); m = read(); K = read(); P = read();
        Set(n); re u, v, w;
        memset(head1, 0, 4 * (n + 1)); memset(headn, 0, 4 * (n + 1));
        while(m--) u = read(), v = read(), w = read(), add(u, v, w, head1), add(v, u, w, headn);
        dij(); printf("%d\n", dfs(1, K));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值