• NOIP 2017逛公园(记忆化搜索)


    逛公园

    题意:在一张有向图中,求出1到n有多少条路径长度不超过最短路+K。

    30分做法:K=0时,就是最短路计数,详见P1144最短路计数

    #include <queue>
    #include <algorithm>
    #include <cstring>
    #include <cstdio>
    #include <iostream>
    #include <cmath>
    #include <queue>
    using namespace std;
    const int maxm = 400007;
    int pre[maxm], other[maxm], len[maxm], l, last[100007];
    int cnt[100007], dis[100007];
    bool vis[100007];
    int t;
    int n, m, k, mo;
    void add(int x, int y, int z) {
        l++;
        pre[l] = last[x];
        last[x] = l;
        other[l] = y;
        len[l] = z;
    }
    priority_queue<pair<int, int> > q;
    void dijkstra() {
        memset(dis, 63, sizeof(dis));
        memset(vis, 0, sizeof(vis));
        memset(cnt, 0, sizeof(cnt));
        dis[1] = 0;
        cnt[1] = 1;
        q.push(make_pair(0, 1));
        while (q.size()) {
            int u = q.top().second;
            q.pop();
            if (vis[u])
                continue;
            vis[u] = 1;
            for (int p = last[u]; p; p = pre[p]) {
                int v = other[p];
                if (dis[v] == dis[u] + len[p])
                    cnt[v] += cnt[u], cnt[v] %= mo;
                if (dis[v] > dis[u] + len[p]) {
                    cnt[v] = cnt[u] % mo;
                    dis[v] = dis[u] + len[p];
                    q.push(make_pair(-dis[v], v));
                }
            }
        }
    }
    int main() {
        scanf("%d", &t);
        while (t--) {
            scanf("%d%d%d%d", &n, &m, &k, &mo);
            l = 0;
            for (int i = 1; i <= n; i++) last[i] = 0;
            for (int i = 1; i <= m; i++) {
                int x, y, z;
                scanf("%d%d%d", &x, &y, &z);
                add(x, y, z);
            }
            if (k == 0) {
                dijkstra();
                cout << cnt[n] << endl;
            }
        }
        return 0;
    }
    

    以下的dis[i]表示到1到i的最短路

    满分做法:考虑DP,因为K<=50,很容易想到f[u][j]表示到u路径长度为dis[u]+j的路径数。f[u][j]一定由f[v][pos]转移过来,v是和u有连边的点(v指向u),pos=dis[i]+j-len[p]-dis[v];因为根据最短路性质,dis[u]<=dis[v]+len[p],dis[u]-dis[v]-leb[p]<=0,所以pos<=j。可以从小到大枚举n结点的j,这样就可以算出n之前所有结点的f值,再向n转移即可。如果pos<0,说明此时dis[v]+len[p]>dis[u]+j,无法转移。我的做法是建反边跑记忆化搜索,这样可以少搜索到一些无用的状态。

    判-1的情况:一条合法路径上出现了零环。因为我们是根据F[n][j]搜索的,所以搜到的状态都是合法的,开一个bool数组记录此状态是否遍历过,因为出现零边的话,pos=j,绕一圈绕到一个被遍历的状态,说明就有零环。

    此外,给1节点直接赋初值不是很好的选择,因为如果1号节点就处在零环中,他的f值就不是1了。对AC没有影响,但还是要理解。在此开一个虚拟节点,连接1号就行了。

    #include <queue>
    #include <algorithm>
    #include <cstring>
    #include <cstdio>
    #include <iostream>
    #include <cmath>
    #include <queue>
    using namespace std;
    typedef long long ll;
    const ll inf = 1e13;
    const int maxm = 200007;
    int pre[maxm], other[maxm], len[maxm], l, last[100007];
    int pre1[maxm], other1[maxm], len1[maxm], l1, last1[100007];
    ll dis[100007];
    bool vis[100007], flag[100007][55], huan;
    int t;
    int n, m, k;
    ll ans, f[100007][55], mo;
    void add(int x, int y, int z) {
        l++;
        pre[l] = last[x];
        last[x] = l;
        other[l] = y;
        len[l] = z;
    }
    void add1(int x, int y, int z) {
        l1++;
        pre1[l1] = last1[x];
        last1[x] = l1;
        other1[l1] = y;
        len1[l1] = z;
    }
    priority_queue<pair<ll, int> > q;
    void dijkstra() {
        for (int i = 1; i <= n; i++) dis[i] = inf;
        memset(vis, 0, sizeof(vis));
        dis[1] = 0;
        q.push(make_pair(0, 1));
        while (q.size()) {
            int u = q.top().second;
            q.pop();
            if (vis[u])
                continue;
            vis[u] = 1;
            for (int p = last[u]; p; p = pre[p]) {
                int v = other[p];
                if (dis[v] > dis[u] + len[p]) {
                    dis[v] = dis[u] + len[p];
                    q.push(make_pair(-dis[v], v));
                }
            }
        }
    }
    inline ll dfs(int x, int pos) {
        if (f[x][pos] != -1)
            return f[x][pos];
        f[x][pos] = 0;
        flag[x][pos] = 1;
        for (int p = last1[x]; p; p = pre1[p]) {
            int v = other1[p];
            int s = pos + dis[x] - len1[p] - dis[v];
            if (s < 0)
                continue;
            if (flag[v][s])
                huan = 1;
            f[x][pos] += dfs(v, s), f[x][pos] %= mo;
        }
        flag[x][pos] = 0;//不要忘了回溯,因为一条路径和另一条路径是两码事
        return f[x][pos];
    }
    int main() {
        scanf("%d", &t);
        while (t--) {
            scanf("%d%d%d%lld", &n, &m, &k, &mo);
            l = 0;
            l1 = 0;
            huan = 0;
            ans = 0;
            for (int i = 1; i <= n + 1; i++) last[i] = 0, last1[i] = 0;
            memset(flag, 0, sizeof(flag));
            memset(f, -1, sizeof(f));
            for (int i = 1; i <= m; i++) {
                int x, y, z;
                scanf("%d%d%d", &x, &y, &z);
                add(x, y, z);
                add1(y, x, z);
            }
            dijkstra();
            add1(1, n + 1, 0);
            f[n + 1][0] = 1;
            dis[n + 1] = 0;
            for (int i = 0; i <= k; i++) ans = (ans + dfs(n, i)) % mo;
            if (huan)
                cout << "-1" << endl;
            else
                printf("%lld
    ", ans);
        }
        return 0;
    }
    
  • 相关阅读:
    排序算法(一)冒泡法
    java是传值还是传引用
    赫夫曼树与赫夫曼编码
    数据结构的相关概念
    字符集和字符编码的区别
    redis为什么选择单线程工作模型
    GET和POST请求的核心区别
    MySQL数据类型及后面小括号的意义
    java中的数据类型
    Jedis无法连接centOS7上的redis
  • 原文地址:https://www.cnblogs.com/lihan123/p/11656727.html
Copyright © 2020-2023  润新知