• 【luogu P3953 逛公园】 题解


    题目链接:https://www.luogu.org/problemnew/show/P3953

    题外话:感觉2017年神题好多。。这还不是最神的一道,真在考场上我也就写个最短路计数暴力了。现在在大佬们的帮助下算是理解了些。

    方便起见,均设路径为 u—>v 权值为w

    首先,看到这个题,我们想到的是最短路计数。

    最短路计数的转移是什么?

    $ if(dis[v] == dis[u] + e[i].len) ans[e[i].to] = (ans[e[i].to] + ans[now]); $

    $ if(dis[e[i].to] > dis[now] + e[i].len) ans[e[i].to] = ans[now]; $

    注:ans[i]表示起点到i点的最短路径数

    20分代码如下:

    (SPFA又暴毙了?)

    #include <queue>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int maxn = 200010;
    int dis[maxn], ans[maxn], n, m, k, T, p;
    bool vis[maxn];
    struct edge{
        int next, to, len;
    }e[maxn<<2];
    int head[maxn], cnt;
    queue<int> q;
    void add(int u, int v, int w)
    {
        e[++cnt].len = w;
        e[cnt].next = head[u];
        e[cnt].to = v;
        head[u] = cnt;
    }
    int Ans;
    void SPFA()
    {
        while(!q.empty())
        {
            int now = q.front(); q.pop();
            vis[now] = 0;
            for(int i = head[now]; i != -1; i = e[i].next)
            {
                if(dis[e[i].to] == dis[now] + e[i].len)
                {
                    ans[e[i].to] = (ans[e[i].to] + ans[now]);
                    if(!vis[e[i].to])
                    {
                        q.push(e[i].to);
                        vis[e[i].to]=true;
                    }
                }
                if(dis[e[i].to] > dis[now] + e[i].len)
                {
                    dis[e[i].to] = dis[now] + e[i].len;
                    ans[e[i].to] = ans[now];
                    if(!vis[e[i].to] /*&& ans[e[i].to]*/)
                    {
                        q.push(e[i].to);
                        vis[e[i].to] = 1;
                    }
                }
            }
            if(now==n)	Ans+=ans[now];
            ans[now]=0;
        }
    }
    int main()
    {
        scanf("%d",&T);
        while(T--)
        {
            memset(head, -1, sizeof(head));
            memset(dis, 127, sizeof(dis));
            memset(vis, 0, sizeof(vis));
            memset(ans, 0, sizeof(ans));
            while(!q.empty()) q.pop();
            scanf("%d%d%d%d",&n,&m,&k,&p);
            for(int i = 1; i <= m; i++)
            {
                int u, v, w;
                scanf("%d%d%d",&u,&v,&w);
                add(u, v, w);
            }
            dis[1] = 0;
            vis[1] = 1;
            ans[1] = 1;
            q.push(1);
            SPFA();
            printf("%d
    ",Ans); 
            Ans=0;
        }
    }
    

    带劲!

    接着看到题目要求计算<=k的路径数,我们考虑是不是可以在ans这玩意后再开一维?

    我们设f[i][j]表示当前第i个点,与到n的最短路相差j的方案数。然后沿前驱递归回去计算。

    于是有转移方程:

    (f[u][j] = Σf[v][dis[u]+j-w-dis[v]])

    u是当前点,v是它的前驱,w是它与它前驱的边的权值,j是相差为j,dis[x]表示1—>x的最短路

    这个方程我们可以用类似前缀和的想法解释:

    当前的dis[u] + j可以理解成我目前到点N可以消耗的路径长度,而dis[v] + w便是如果v点转移到u上去所需要消耗的路径长度,相减若大于等于0,这说明我们是可以从v点过来的,那么这样一直到点1,直到不能再有可以消耗的差值,我们就说明找到了一条从1—>n差值为j的路径。所以初始的状态为f[1][0] = 1。

    最后相差为j的答案就会存储在dp[n][j]里。

    所以我们要建一个反着的图,为了方便我们枚举前驱。

    于是:

    for(i = 1; i <= k; i++)
    ans += f[n][i];

    这时候..考虑怎么判断0环?

    如果有0环的话,我的dp方程转移会被转移多次,所以我们用一个used判断是不是被转移多次就好了。

    下面用记忆化搜索实现

    code:

    #include <queue>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int maxn = 200010;
    int dis[maxn], ans, n, m, mod, k, flag, T, f[maxn][51], used[maxn][51];
    bool vis[maxn];
    struct edge{
        int to, next, len;
    }e1[maxn<<2], e2[maxn<<2];//正图1   反图2 
    int head1[maxn], head2[maxn], cnt1, cnt2;
    void add1(int u, int v, int w)
    {
        e1[++cnt1].len = w;
        e1[cnt1].next = head1[u];
        e1[cnt1].to = v;
        head1[u] = cnt1;
    }
    void add2(int u, int v, int w)
    {
        e2[++cnt2].len = w;
        e2[cnt2].next = head2[u];
        e2[cnt2].to = v;
        head2[u] = cnt2;
    }
    //----------------------------------
    void SPFA()
    {
        queue<int> q;
        memset(dis, 64, sizeof(dis));
        memset(vis, 0, sizeof(vis));
        dis[1] = 0, vis[1] = 1;
        q.push(1);
        while(!q.empty())
        {
            int now = q.front(); q.pop();
            vis[now] = 0;
            for(int i = head1[now]; i != -1; i = e1[i].next)
            {
                if(dis[e1[i].to] > dis[now] + e1[i].len)
                {
                    dis[e1[i].to] = dis[now] + e1[i].len;
                    if(!vis[e1[i].to])
                    {
                        q.push(e1[i].to);
                        vis[e1[i].to] = 1;
                    }
                }
            }
        }
    }
    //----------------------------------
    int dfs(int u, int k)
    {
        if(~f[u][k]) return f[u][k];
        used[u][k] = 1;
        f[u][k] = 0;
        for(int i = head2[u]; i != -1; i = e2[i].next)
        {
            int v = e2[i].to, w = e2[i].len;
            int t = dis[u] + k - w - dis[v];
            if(t < 0) continue;
            if(used[v][t]) flag = 1;
            f[u][k] = (f[u][k] + dfs(v, t)) % mod;	
        }
        used[u][k] = 0;
        return f[u][k];
    }
    //----------------------------------
    void init()
    {
        memset(f, -1, sizeof(f));
        memset(used, 0, sizeof(used));
        memset(e1, 0, sizeof(e1));
        memset(e2, 0, sizeof(e2));
        memset(head1, -1, sizeof(head1));
        memset(head2, -1, sizeof(head2));
        cnt1 = 0, cnt2 = 0, ans = 0, flag = 0;
    }
    int main()
    {
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d%d%d",&n,&m,&k,&mod);
            init();
            for(int i = 1; i <= m; i++)
            {
                int u, v, w;
                scanf("%d%d%d",&u, &v, &w);
                add1(u, v, w);
                add2(v, u, w);
            }
            SPFA();
            f[1][0] = 1;
            for(int i = 0; i <= k; i++)
            ans = (ans + dfs(n, i))%mod;
            dfs(n, k+1);
            if(flag) {printf("-1
    "); continue;}
            printf("%d
    ",ans);
        }
        return 0;
    }
    

    (SPFA没有死!)

  • 相关阅读:
    Thrift在微服务中的使用
    MySQL 必知必会
    IDEA 内使用 git
    分布式锁
    LeetCode 图
    LeetCode 位运算
    LeetCode 数组
    LeetCode 字符串
    LeetCode 哈希表
    LeetCode 栈和队列
  • 原文地址:https://www.cnblogs.com/MisakaAzusa/p/9818933.html
Copyright © 2020-2023  润新知