P3953 [NOIP2017 提高组] 拓扑排序 + Dijkstra + DP

题意

传送门 P3953 [NOIP2017 提高组] 逛公园

题解

首先处理合法路线无穷多的情况。合法路线无穷多当且仅当图中出现权值和为 0 0 0 的环,且存在一条经过环上的点且权值和不超过 D + K D+K D+K 的路径。

求解这样的环,可以对边权为 0 0 0 的边建正图以及反图,分别进行拓扑排序。那么权值和为 0 0 0 的环上的点 x x x 满足下述两个条件:存在与 x x x 相连的边权为 0 0 0 的边; x x x 在正、反图拓扑排序中入度始终大于 0 0 0。原理是正、反图拓扑排序分别能够处理与环相连的入边与出边。

若存在这样的环,需要枚举环上的点判断是否存在一条经过该点且权值和不超过 D + K D+K D+K 的路径。同样可以通过建原图的正图以及反图进行求解。应用 D i j k s t r a Dijkstra Dijkstra 在正、反图上分别求解以 0 , N − 1 0,N-1 0,N1 为源点的单源最短路,分别记为 d s , r d s ds,rds ds,rds。那么经过 x x x 的路径最短长度为 d s [ x ] + r d s [ x ] ds[x]+rds[x] ds[x]+rds[x]

若合法路线数量有限,对答案有贡献的点 x x x 都满足 d s [ x ] + r d s [ x ] ≤ D + K ds[x]+rds[x]\leq D+K ds[x]+rds[x]D+K,此时则不会处理边权为 0 0 0 的环上的点。那么搜索路径时,若从点 x x x 出发又返回 x x x,必定经过正权环。经过 x x x 且满足权值和为 [ D , D + K ] [D,D+K] [D,D+K] 的路径,在 x x x 到汇点 N − 1 N-1 N1 的这一段路径,权值和只可能为 [ r d s [ x ] , r d s [ x ] + K ] \big[rds[x],rds[x]+K\big] [rds[x],rds[x]+K]。那么可以用 K + 1 K+1 K+1 个状态对这样的路径进行表示。 d p [ x ] [ d ] dp[x][d] dp[x][d] 代表从节点 x x x 出发到 N − 1 N-1 N1 且长度为 r d s [ x ] + d rds[x]+d rds[x]+d 的路径数量,这样的状态搜索满足 D A G DAG DAG 的性质,可以记忆化求解
d p [ x ] [ d ] = ∑ d p [ y ] [ d + r d s [ x ] − c o s t ( x , y ) − r d s [ y ] ] , e ( x , y ) ∈ G dp[x][d]=\sum dp[y][d+rds[x]-cost(x,y)-rds[y]], e(x,y)\in G dp[x][d]=dp[y][d+rds[x]cost(x,y)rds[y]],e(x,y)G 只有满足 d s [ x ] + r d s [ x ] + d ≤ D + K ds[x]+rds[x]+d\leq D+K ds[x]+rds[x]+dD+K 的状态可能对答案有贡献,那么搜索时可以对不满足条件的状态进行剪枝。总时间复杂度 O ( N log ⁡ M + N K ) O(N\log M+NK) O(NlogM+NK)

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int maxk = 55, maxn = 100005;
struct edge
{
    
    
    int to, cost;
};
int T, N, M, K, P, maxlen, d0[maxn], rd0[maxn];
int ds[maxn], rds[maxn], mem[maxn][maxk];
vector<edge> G[maxn], rG[maxn], G0[maxn], rG0[maxn];
bool used[maxn], loop[maxn];

void topsort(vector<edge> G[], int in[])
{
    
    
    queue<int> q;
    for (int i = 0; i < N; ++i)
        if (!in[i])
            q.push(i);
    while (q.size())
    {
    
    
        int x = q.front();
        q.pop();
        for (auto &e : G[x])
            if (!--in[e.to])
                q.push(e.to);
    }
    for (int i = 0; i < N; ++i)
        loop[i] &= in[i];
}

void dijkstra(vector<edge> G[], int s, int ds[])
{
    
    
    priority_queue<pii, vector<pii>, greater<pii>> q;
    memset(ds, 0x3f, sizeof(int) * N);
    memset(used, 0, sizeof(bool) * N);
    ds[s] = 0, q.push({
    
    0, s});
    while (q.size())
    {
    
    
        int x = q.top().second;
        q.pop();
        if (used[x])
            continue;
        used[x] = 1;
        for (auto &e : G[x])
        {
    
    
            int y = e.to, d = ds[x] + e.cost;
            if (d < ds[y])
                ds[y] = d, q.push({
    
    d, y});
        }
    }
}

int rec(int x, int d)
{
    
    
    if (mem[x][d] != -1)
        return mem[x][d];
    int res = 0;
    for (auto &e : G[x])
    {
    
    
        int y = e.to, d2 = d + rds[x] - e.cost - rds[y];
        if (0 <= d2 && d2 <= K && d2 + rds[y] + ds[y] <= maxlen)
            res = (res + rec(y, d2)) % P;
    }
    return mem[x][d] = res;
}

int main()
{
    
    
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> T;
    while (T--)
    {
    
    
        cin >> N >> M >> K >> P;
        memset(d0, 0, sizeof(int) * N);
        memset(rd0, 0, sizeof(int) * N);
        memset(loop, 0, sizeof(bool) * N);
        for (int i = 0; i < N; ++i)
            G[i].clear(), rG[i].clear(), G0[i].clear(), rG0[i].clear();
        for (int i = 0; i < M; ++i)
        {
    
    
            int a, b, c;
            cin >> a >> b >> c;
            --a, --b;
            G[a].push_back(edge{
    
    b, c}), rG[b].push_back(edge{
    
    a, c});
            if (c == 0)
            {
    
    
                loop[a] = loop[b] = 1;
                ++d0[b], --rd0[a];
                G0[a].push_back(edge{
    
    b, c}), rG0[b].push_back(edge{
    
    a, c});
            }
        }
        topsort(G0, d0), topsort(rG0, rd0);
        dijkstra(G, 0, ds), dijkstra(rG, N - 1, rds);
        maxlen = ds[N - 1] + K;
        bool is_inf = 0;
        for (int i = 0; i < N; ++i)
            if (loop[i] && ds[i] + rds[i] <= maxlen)
            {
    
    
                is_inf = 1;
                break;
            }
        if (is_inf)
        {
    
    
            cout << -1 << '\n';
            continue;
        }
        memset(mem, -1, sizeof(mem));
        mem[N - 1][0] = 1;
        int res = 0;
        for (int i = 0; i <= K; ++i)
            res = (res + rec(0, i)) % P;
        cout << res << '\n';
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/neweryyy/article/details/119169733