洛谷P3953 逛公园
? 解题记录 ? ? 洛谷 ? ? 最短路 ? ? 动态规划 ?    2018-10-09 08:25:53    437    0    0

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

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

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

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

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

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

输入格式

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

接下来 T 组数据,对于每组数据:

第一行包含四个整数 N,M,K,P, 每两个整数之间用一个空格隔开。

接下来 M 行,每行三个整数 ai,bi,ci,代表编号为 ai,bi 的点之间有一条权值为 ci 的有向边,每两个整数之间用一个空格隔开。

输出格式

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

样例一

input

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

output

3
-1

explanation

对于第一组数据,最短路为 3

15 , 1245 , 1235 为 3 条合法路径。

样例二

见样例数据下载。

限制与约定

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

测试点编号TNMK是否有0边
155100
25100020000
351000200050
451000200050
551000200050
651000200050
751000002000000
8310000020000050
9310000020000050
10310000020000050

对于 100% 的数据,1P1e91ai,biN0ci1000

去年NOIP这道题只有10分……

感觉自己去年好菜啊,不知道在干什么……

这道题先预处理出每一个点u到终点的最短路exp[u],可以通过反向边最短路处理。

考虑一个点u,我们只关心到终点距离dis[u] <= exp[u] + k的方案,因为一旦距离超过exp[u] + k那么即使从起点一直走最短路到这里,误差还是大于k的。

设dp[u][k]表示从u到终点,最短路误差为k的方案数。

假设边权w>0,发现这个图是一个DAG,因为一层以内是一个最短路跑出来的图,最短路跑出来一定是DAG,并且一层只会向下层转移,也是一个DAG,这里就可以记忆化搜索。最后dp[1][0~k]就是答案。

那么假设边权>=0呢?那么单层内的图可能成环。发现如果在记忆化搜索dp[1][0~k]的过程中搜出了环一定就是无限大的。输出-1即可。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int maxn = 1e5 + 5;
const int maxm = 2e5 + 5;

struct edge {
    int v, w, next;
} e[maxm << 2];
int head[maxn], cnt;
int rev[maxn], dp[maxn][52], ban[maxn][52], via[maxn][52];
int n, m, k, p, u, v, w;
void Add(int &x, const int &a) {
    x += a; if(x >= p) x -= p;
}
void addr(const int &u, const int &v, const int &w) {
    e[++cnt] = (edge) {v, w, rev[u]};
    rev[u] = cnt;
}
void adde(const int &u, const int &v, const int &w) {
    e[++cnt] = (edge) {v, w, head[u]};
    head[u] = cnt;
}

int vis[maxn], dis[maxn];
queue<int > q;
void SPFA(int * head, int S, int T) {
    memset(dis, 0x3f, sizeof(dis));
    q.push(S), vis[S] = 1, dis[S] = 0;
    while(!q.empty()) {
        int u = q.front(); 
        q.pop(), vis[u] = 0;
        for(register int i = head[u]; i; i = e[i].next) {
            int v = e[i].v;
            if(dis[u] + e[i].w < dis[v]) {
                dis[v] = dis[u] + e[i].w;
                if(vis[v]) continue;
                vis[v] = 1, q.push(v);
            }
        }
    }
}

int flag = 0;
int dfs(int u, int mv) {
    if(flag == 1) return 0;
    if(!ban[u][mv] && via[u][mv]) {
        printf("-1\n"), flag = 1;
        return 0;
    }
    if(ban[u][mv]) {
        return dp[u][mv];
    }
    via[u][mv] = 1;
    for(register int i = head[u]; i; i = e[i].next) {
        int v = e[i].v;
        int nmv = mv + dis[u] - dis[v] - e[i].w;
        //int mv = dis[v] + e[i].w + nmv - dis[u];
        if(nmv >= 0) Add(dp[u][mv], dfs(v, nmv));
    }
    ban[u][mv] = 1;
    return dp[u][mv];
}

int main() {
    //freopen("t5.in", "r", stdin);
    //freopen("t5.out", "w", stdout);
    int t = 0;
    scanf("%d", &t);
    while(t--) {
        cnt = 0;
        memset(ban, 0, sizeof(ban));
        memset(via, 0, sizeof(via));
        memset(dp, 0, sizeof(dp));
        memset(head, 0, sizeof(head));
        memset(rev, 0, sizeof(rev));
        scanf("%d%d%d%d", &n, &m, &k, &p);
        for(register int i = 1; i <= m; ++i)
            scanf("%d%d%d", &u, &v, &w), adde(u, v, w), addr(v, u, w);
        SPFA(rev, n, m);
        dp[n][0] = 1;
        int ans = 0;
        for(register int i = 0; i <= k; ++i) {
            flag = 0, dfs(1, i), Add(ans, dp[1][i]);
            if(flag) break;
        }
        if(!flag) printf("%d\n", ans);
    }
    return 0;
}


上一篇: 洛谷P3960 列队

下一篇: 洛谷P2822 组合数问题

437 人读过
立即登录, 发表评论.
没有帐号? 立即注册
0 条评论
文档导航