NOIP2017 逛公园 记忆化搜索|dp(已过hack数据)

30pts

可以发现,k=0的情况下,问题转化为最短路计数,即从起点s到每个点有多少最短路。

跑最短路的时候顺便维护ans[u],表示从su的最短路方案,讨论如下:

①当dist[v]>dist[u]+val[u][v]时,ans[v]=ans[u];

②当dist[v]=dist[u]+val[u][v]时,ans[v]+=ans[u]

答案即是ans[n]

100pts

1.动态规划

首先不考虑w=0的边的处理。

容易发现,k50,故考虑枚举k的值,问题转化为求解每个k下的最短路方案数个数。

dp[u][k]表示当前在第u个点,最短路距离为dist[u]+k的方案数。答案即为i=050dp[n][i]

想要得知终点的dp方程状态,那么从起点建正图来推状态是比较复杂的,故不妨从终点n倒推状态。

考虑dp[v][x]>dp[u][k]的状态转移,发现k可以由v转移过来:

(dist[v]+x)+val[v][u]=dist[u]+k

k=dist[v]+x+val[v][u]dist[u]

dp[u][k]=vdp[v][dist[v]+x+val[v][u]dist[u]]

复杂度O(km)

2.无穷多解处理

可以发现,若通过的道路上出现一个权值均为0的环,那一定会出现无穷多解。但有零环不代表一定存在无穷解

考虑一个特殊数据:

image

显然的是,2-4的零环并不影响最短路方案,但是4-5-6的零环会产生无穷多解。

故我们首先用tarjan找出零环,对这条零环上的点u(任一点即可,对答案的贡献一致)进行如下判断[1]

dist[u]+dist2[u]dis[n]+k

其中dist[u]代表1u的最短路,dist2[u]代表un的最短路。

那么说明这条零环位于最短路上,说明存在无穷解。

综上:

对整张图分别正反建边,并跑一遍最短路;跑一遍tarjan寻找零环,判断零环是否导致无穷解即可。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<stack>

int T;
int n, m, k, p;
int X, Y, Z;
int ans;
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;

int nxt[maxn << 1], to[maxn << 1], head[maxn], val[maxn << 1];
int nxt2[maxn << 1], to2[maxn << 1], head2[maxn], val2[maxn << 1];
int nxt3[maxn << 1], to3[maxn << 1], head3[maxn], val3[maxn << 1];
int dis[maxn], dis2[maxn];
int tot1, tot2, tot3;
bool vis1[maxn << 1], vis2[maxn << 1];

inline void add1(int a, int b, int c) {
    to[++tot1] = b;
    val[tot1] = c;
    nxt[tot1] = head[a];
    head[a] = tot1;
}

inline void add2(int a, int b, int c) {
    to2[++tot2] = b;
    val2[tot2] = c;
    nxt2[tot2] = head2[a];
    head2[a] = tot2;
}

inline void add3(int a, int b, int c) {
    to3[++tot3] = b;
    val3[tot3] = c;
    nxt3[tot3] = head3[a];
    head3[a] = tot3;
}


inline void dijsktra() {
    memset(dis, inf, sizeof(dis));
    memset(vis1, 0, sizeof(vis1));
    dis[1] = 0;
    std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int> >, std::greater<std::pair<int, int> > > q;
    q.push(std::make_pair(0, 1));
    while (!q.empty()) {
        int u = q.top().second;
        q.pop();
        if (vis1[u]) continue;
        vis1[u] = 1;
        for (int i = head[u]; i; i = nxt[i]) {
            int v = to[i];
            //if (vis[v]) continue;
            if (dis[v] > dis[u] + val[i]) {
                dis[v] = dis[u] + val[i];
                q.push(std::make_pair(dis[v], v));
            }
        }
    }
}

inline void oppodijsktra() {
    memset(dis2, inf, sizeof(dis2));
    memset(vis2, 0, sizeof(vis2));
    dis2[n] = 0;
    std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int> >, std::greater<std::pair<int, int> > > q2;
    q2.push(std::make_pair(0, n));
    while (!q2.empty()) {
        int u = q2.top().second;
        q2.pop();
        if (vis2[u]) continue;
        vis2[u] = 1;
        for (int i = head2[u]; i; i = nxt2[i]) {
            int v = to2[i];
            //if (vis[v]) continue;
            if (dis2[v] > dis2[u] + val2[i]) {
                dis2[v] = dis2[u] + val2[i];
                q2.push(std::make_pair(dis2[v], v));
            }
        }
    }
}

int dfn[maxn], low[maxn];
bool vis[maxn], isZero[maxn];//isZero判断是否为零环上点
int cnt;
std::stack<int> st;

inline void tarjan(int x) {
    dfn[x] = low[x] = ++cnt;
    st.push(x);
    vis[x] = 1;

    for (int i = head3[x]; i; i = nxt3[i]) {
        int v = to3[i];
        if (!dfn[v]) {
            tarjan(v);
            low[x] = std::min(low[x], low[v]);
        } else if (vis[v]) {
            low[x] = std::min(low[x], dfn[v]);
        }
    }

    if (dfn[x] == low[x]) {
        int sum = 0, temp;
        do {
            temp = st.top();
            st.pop();
            vis[temp] = 0;
            sum++;

        } while (temp != x);
        isZero[x] = 1;
        if (sum == 1) isZero[x] = 0;//0点(且已知不存在自环)不属于零环
    }
}

bool flag = 0;

int dp[maxn][55];

int dfs(int x, int k) {
    if (dp[x][k] != -1) return dp[x][k];
    dp[x][k] = 0;
    for (int i = head2[x]; i; i = nxt2[i]) {
        int v = to2[i], w = val2[i];
        int temp = dis[x] + k - (dis[v] + w);
        if (temp < 0) continue;
        dp[x][k] = (dp[x][k] + dfs(v, temp)) % p;
    }
    return dp[x][k];
}

inline void clear() {
    memset(nxt, 0, sizeof(nxt));
    memset(to, 0, sizeof(to));
    memset(val, 0, sizeof(val));

    memset(nxt2, 0, sizeof(nxt2));
    memset(to2, 0, sizeof(to2));
    memset(val2, 0, sizeof(val2));

    memset(nxt3, 0, sizeof(nxt3));
    memset(to3, 0, sizeof(to3));
    memset(val3, 0, sizeof(val3));

    for (int i = 1; i <= n; i++)
        head[i] = head2[i] = head3[i] = low[i] = dfn[i] = isZero[i] = vis[i] = 0;
    ans = flag = 0;
    tot1 = tot2 = tot3 = 0;
}

int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d%d%d", &n, &m, &k, &p);
        clear();
        while (m--) {
            scanf("%d%d%d", &X, &Y, &Z);
            add1(X, Y, Z);
            add2(Y, X, Z);
            if (Z == 0) add3(X, Y, Z);
        }
        dijsktra();
        oppodijsktra();

        for (int i = 1; i <= n; i++)
            if (!dfn[i]) tarjan(i);
        for (int i = 1; i <= n; i++) {
            if (isZero[i]) {
                if (dis[i] + dis2[i] <= dis[n] + k) {
                    flag = 1;
                    break;
                }
            }
        }
        if (flag) {
            printf("-1\n");
            continue;
        }//判断会产生-1解的0环
        memset(dp, -1, sizeof(dp));
        dp[1][0] = 1;
        for (int i = 0; i <= k; i++)
            ans = (ans + dfs(n, i)) % p;
        printf("%d\n", ans);
    }
    return 0;
}

参考:

[1].^零环与无穷多解关系的证明与思路

posted @   SxtoxA  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
12 13
点击右上角即可分享
微信分享提示