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

159 篇文章 1 订阅
题意

传送门 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞赛有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞赛是提高专业知识和技能水平的有效途径。通过参与竞赛,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞赛过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞赛培养了学生的团队合作精神。许多竞赛项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞赛是提高学生综合能力的一种途径。竞赛项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参赛者具备全面的素质。在竞赛过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞赛可以为学生提供展示自我、树立信心的机会。通过比赛的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞赛对于个人职业发展具有积极的助推作用。在竞赛中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞赛奖项不仅可以作为个人履历的亮点,还可以为进入理想的工作岗位提供有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值