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\n", ans);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/lihan123/p/11656727.html
今日推荐