NOIP2017d1t3逛公园|题解

NOIP2017d1t3逛公园|题解

九月 18, 2019

NOIP2017d1t3逛公园题解

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

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

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

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

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

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

AFO时长一年后难得又写了一道题.

这个题看起来有些复杂. 我们不妨先考虑弱些的情况

保证公园的地图是一张DAG

这个情形就很显然是个树形dp. 不难设计出状态转移方程

设$dis[u]$是”从$u$到终点的最短路”, $moew(u,hp)$表示”策策处在$u$点, 体力为$dis[u]+hp$时, 恰好耗尽体力走到终点的方案数”, 容易得到转移方程:
$$
meow(u,hp)=\sum_{v} meow(v,dis[u]+hp-edge[u\ to\ v].length-dis[v])
$$
其中$v$是所有$u$可以直接连向的点

由于$0\leq hp\leq k\leq$, 故复杂度为$O(n\times hp)$

地图中存在环, 但保证不存在零环

这个时候上述转移方程还是否适用呢?

笔者在这里陷入了一个误区: 如果存在环的话, 策策就可以多次走到同一个点. 这样一个点就会被多次处理, 看起来不具备无后效性

请注意: 我们所设计的状态不是 “策策处在$u$点” , 而是 “体力为$dis[u]+hp$的策策处在$u$点”

如果不存在零环的话, 策策虽然可以多次走到$u$点, 但是每次走到$u$点时的$hp$是一定不同的. 永远只有一个“处在$u$点的体力为$dis[u]+hp$的策策”.

也就是说$meow(u,hp)$的值一定只会被计算一次, 满足无后效性. 上述方程仍然成立. 时间复杂度依然为$O(n\times hp)$

(AFO久了脑子就不太好使了.)

地图中存在零环

顺着上面的思路, 没有零环时每次走到$u$点时的$hp$是一定不同的. 那么也就是说”有零环时会有两次走到$u$点时$hp$是相同的”. 通过这个性质就可以轻松地判断$u$点上是否在一个零环上.

值得注意的特殊情况:

$meow(终点,0)=1$是dp的边界条件, 那么使用上述的判零环方法会忽略”终点在一个零环上”的情况. 因为这种情况发生时$moew(终点,0)=+\infty$, dp的边界条件被破坏了.

特判”终点在一个零环上”的情况也不难. 只要在做dijkstra的过程中发现有一条指向终点的长度为$0$的最短路, 那么这条最短路就是终点上的零环.

(实话说这个情况要不是样例有我就真的忽略了)

总之代码如下

#include <bits/stdc++.h>
#define LL long long
using namespace std;
LL n, m, k, p, T;
//gragh beg
struct edge{
    LL v, cs;
    edge *nxt;
    edge (const LL &be, const LL &ge, edge *de){
        v = be, cs = ge, nxt = de;
        return ;
    }
}*G[200000], *G_[200000];
void del_g(edge **g, LL n){
    for (LL i = 1;i <= n;++ i){
        for (edge *j = g[i], *k;j;k = j, j = j->nxt, delete(k));
        g[i] = NULL;
    }
    return ;
}
//gragh end
//dijkstra beg
LL dis[200000];
bool vis[200000], zero_1;
struct qwq{
    LL v, dis;
    bool operator < (const qwq &al)const{
        return dis > al.dis;
    }
};
void dijkstra(LL s){
    priority_queue<qwq> q;
    q.push((qwq){1, 0});
    while(q.size()){
        qwq al = q.top();
        q.pop();
        if (vis[al.v])
            continue;
        dis[al.v] = al.dis;
        vis[al.v] = 1;
        for (edge *i = G[al.v]; i; i = i->nxt){
            if (vis[i->v]){
                if (i->v == 1 && dis[al.v] + i->cs == 0){
                    //node1 on a zero circle
                    zero_1 = 1;
                    return ;
                }
                continue;
            }
            q.push((qwq){i->v, al.dis + i->cs});
        }
    }
    return ;
}
//dijkstra end
//dp
LL dp[100010][60];
bool sta[100010][60];
LL meow(LL u, LL hp){
    if (sta[u][hp])
    //a zero circle on this road
        return -1;
    if (dp[u][hp] != -1)
        return dp[u][hp];
    sta[u][hp] = 1;
    LL al = 0, hp_;
    for (edge *i = G_[u];i;i = i->nxt){
        hp_ = hp + dis[u] - i->cs - dis[i->v];
        if (hp_ < 0 || hp_ > k)
            continue;
        LL tmp = meow(i->v, hp_);
        if (tmp == -1)
            return -1;
        al = (al + tmp) % p;
    }
    sta[u][hp] = 0;
    return dp[u][hp] = al;
}
//dp end
int main (){
    cin >> T;
    while (T--){
        //initialization
        memset(dis, 0, sizeof(dis));
        memset(vis, 0, sizeof(vis));
        memset(dp, -1, sizeof(dp));
        memset(sta, 0, sizeof(sta));
        zero_1 = 0;
        LL al, be, ge;
        scanf("%lld%lld%lld%lld", &n, &m, &k, &p);
        for (LL i = 1;i <= m;++ i){
            scanf("%lld%lld%lld", &al, &be, &ge);
            G[al] = new edge(be, ge, G[al]);
            G_[be] = new edge(al, ge, G_[be]);
        }
        //main
        LL ans = 0;
        dijkstra(1);
        if (zero_1){
            ans = -1;
        }
        else{
            dp[1][0] = 1;
            for (LL i = 0;i <= k;++ i){
                LL tmp = meow(n, i);
                if (tmp == -1){
                    ans = -1;
                    break;
                }
                ans = (ans + tmp) % p;
            }
        }
        printf("%lld\n", ans);
        //initialization
        del_g(G, n);del_g(G_, n);
    }
    return 0;
}

文结.