洛谷P3953 [NOIp2017]逛公园

题目地址

分析

  • dis1[u] 表示 1u 的最短路长度, disn[u] 表示 un 的最短路长度

【30pts】拓扑排序 + DP

  • 这里针对的是 K=0 的情况,问题转化为求图中的最短路径数
  • 我们先将最短路图建出来(对于每条边 uv ,满足 dis1[u]+lenuv=dis1[v] ),因为没有 0 边所以就是个有向无环图,直接按照拓扑序计算DP即可

【100pts】记忆化搜索 + DP + Tarjan

  • 首先应该能很容易想到这还是个 DP
  • 然后考虑怎样定义状态:
    • 考场上我想得最为简单暴力,设 g[u][k] 表示到达点 u ,长度为k的路径数,显然 g[u][k]=g[v][klenuv] ,但空间时间都要爆炸,于是粗略算了空间后听天由命……
    • 但其实能注意到 K 的范围相当小,于是我们设f[u][k]表示到达点 u ,长度为dis1[u]+k的路径数,则转移为 f[u][k]=f[v][k(dis1[u]+lenuvdis1[v])]
  • 考虑到边权可能为 0 ,转移的顺序会影响最后的结果,因此我们用记忆化搜索代替直接DP
  • 那么现在只剩下一个问题:如何判无解
    • 显然无解的情况就是在满足条件的路径上出现 0
    • 如何判断一个点u是否在满足条件的路径上?显然我们可以知道这个点满足 dis1[u]+disn[u]dis1[n]+K
    • 我们把所有长度为 0 的边建成一个新图,然后用Tarjan找出所有点数大于 1 的强连通分量(也就是0环),逐个检查上面的点即可
  • 时间复杂度 O(MK)

代码

  • 发觉常数巨大,果然蒟蒻
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>

using namespace std;

inline int get()
{
    char ch; int res = 0; bool Flag = false;
    while (!isdigit(ch = getchar()));
    (ch == '-' ? Flag = true : res = ch ^ 48);
    while (isdigit(ch = getchar()))
        res = res * 10 + ch - 48;
    return Flag ? -res : res;
}

const int Maxn = 0x3f3f3f3f;
const int N = 1e5 + 5, M = N << 1;

int D1[N], Dn[N], h[M], f[N][55], a[N];
int Tm, n, m, K, P, Ans, top, tis; 
int dfn[N], low[N], stk[N]; 
bool vis[N], inv[N], Flag;

struct Edge
{
    int to, cst; Edge *nxt;
};

Edge p[M], *T = p, *lst[N];
Edge q[M], *Q = q, *rst[N];

inline void LinkEdge(int x, int y, int z)
{
    (++T)->nxt = lst[x]; lst[x] = T; 
    T->to = y; T->cst = z;
}

inline void RinkEdge(int x, int y, int z)
{
    (++Q)->nxt = rst[x]; rst[x] = Q; 
    Q->to = y; Q->cst = z;
}

inline void Spfa(int src, int *dis)
{
    for (int i = 1; i <= n; ++i) dis[i] = Maxn;
    int t = 0, w = 1, x, y; dis[h[1] = src] = 0; 
    while (t != w)
    {
        ++t; if (t == M) t = 0;
        vis[x = h[t]] = false;
        for (Edge *e = src == 1 ? lst[x] : rst[x]; e; e = e->nxt)
            if (dis[y = e->to] > dis[x] + e->cst)
            {
                dis[y] = dis[x] + e->cst;
                if (!vis[y]) 
                {
                    ++w; if (w == M) w = 0;
                    vis[h[w] = y] = true;
                }
            }
    }
}

inline void Add(int &x, int y)
{
    x += y; if (x >= P) x -= P; 
}

inline int Dfs(int x, int d)
{
    if (f[x][d] != -1) return f[x][d];
    if (x == n && !d) return f[x][d] = 1;
    f[x][d] = 0;
    for (Edge *e = lst[x]; e; e = e->nxt)
    {
        int tmp = d - e->cst;
        if (tmp >= 0 && tmp <= K)
            Add(f[x][d], Dfs(e->to, tmp));
    }
    return f[x][d];
}

inline void CkMin(int &x, int y)
{
    if (x > y) x = y;
}

inline void Tarjan(int x)
{
    if (Flag) return ;
    dfn[x] = low[x] = ++tis; int y;
    inv[x] = true; stk[++top] = x;
    for (Edge *e = rst[x]; e; e = e->nxt)
    {
        if (e->cst) continue;
        if (!dfn[y = e->to]) 
            Tarjan(y), CkMin(low[x], low[y]);
        else if (inv[y])
            CkMin(low[x], dfn[y]);
    }

    if (dfn[x] == low[x])
    {
        inv[x] = false; bool num = false;
        while (y = stk[top--], y != x)
            inv[y] = false, num = true;
        if (D1[x] + Dn[x] <= D1[n] + K && num) Flag = true;
    } 
}

int main()
{
//  freopen("park.in", "r", stdin);
//  freopen("park.out", "w", stdout);

    Tm = get();
    while (Tm--)
    {
        memset(lst, 0, sizeof(lst)); T = p;
        memset(rst, 0, sizeof(rst)); Q = q;
        int x, y, z;
        n = get(); m = get(); K = get(); P = get();
        while (m--)
        {
            x = get(); y = get(); z = get();
            LinkEdge(x, y, z);
            RinkEdge(y, x, z);
        }

        Spfa(1, D1); Spfa(n, Dn);
        memset(dfn, 0, sizeof(dfn));
        memset(inv, false, sizeof(inv)); Flag = false;
        for (int i = 1; i <= n; ++i)
            if (!dfn[i]) Tarjan(i);
        if (Flag) 
        {
            puts("-1");
            continue;
        }

        for (int i = 1; i <= n; ++i)
            for (Edge *e = lst[i]; e; e = e->nxt)
                e->cst += D1[i] - D1[e->to];

        for (int k = 0; k <= K; ++k)
            for (int i = 1; i <= n; ++i)
                f[i][k] = -1;
        Ans = 0;
        for (int k = 0; k <= K; ++k)
            Add(Ans, Dfs(1, k));
        printf("%d\n", Ans);
    }

//  fclose(stdin); fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值