Loading

Magic

题目

由于没找到原题, 只能挂上 pdf
题目下载

算法

暴力

标准的 dp + 图论(可是考前并没有听说过)

于是推出式子
定义 \(dp_{u, g}\) 为从起点到 \(u\), 经过的边权的最大公倍数为 \(g\) 的最短路, 枚举 \(u\) 的出边 \(v\)

\[dp_{v, g^{\prime}} = \min_{exist (u \rightarrow v)}(dp_{u, g} + \frac{w}{g^{\prime}}) \]

转移时显然只能从出边中找, 并且由于不是 DAG, 需要想办法收敛状态
于是想到 spfa 跑一遍 (不知道这种情况下 spfa 是死是活, 疑似有利用 dp 单调性的更优算法)
平均时间复杂度 \(O(qm\sqrt{m})\)

正解

挂个 pdf
题解下载

想到之前一个同学, 遂下定决心 A 了这道题

终点一定, 大概要倒着推 dp
问题在于边权实在不好表示
因为边权只与起点到这个点的 \(\gcd\) 有关
定义 \(dp_{u, g}\) 表示 \(u\)\(n\) 点, 从起点到 \(u\)\(\gcd\)\(g\)

枚举 \(v\) 的出边 \(u\)

\[dp_{u, g} = \min_{exist (v \rightarrow u)}(dp_{u} + \frac{w}{g^{\prime}}) \]

枚举 \(g\)\(g^{\prime}\) 倍数即可转移

能把时间复杂度优化到平均 \(O(mV + Q)\)
感兴趣可以把我的 spfa 卡了, 让我开开眼

代码

暴力

#include <bits/stdc++.h>
#define int long long
const int MAXM = 2024;
const int MAXN = 1024;
const int INF = INT_MAX;

int n, m;

struct edge
{
    int to;
    int w;
    int nxt;
} Edge[MAXM << 1]; //
int head[MAXN];
int cnt = 0;

void init()
{
    for (int i = 1; i <= n; i++)
    {
        head[i] = -1;
    }
}

void addedge(int u, int v, int w)
{
    Edge[++cnt].to = v;
    Edge[cnt].w = w;
    Edge[cnt].nxt = head[u];
    head[u] = cnt;
}

int Q;

bool inq[MAXN][520];
int dp[MAXN][520]; //这题真能用来表白了吧..

struct node
{
    int Point;
    int Gcd;
};
std::queue<node> CanRelax;
void spfa(int from)
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= 500; j++)
        {
            dp[i][j] = INF;
        }
    }
    memset(inq, false, sizeof(inq));
    for (int i = 1; i <= 500; i++)
    {
        dp[from][i] = 0;
        CanRelax.push((node){from, i});
        inq[from][i] = true;
    }

    while(!CanRelax.empty())
    {
        node Now = CanRelax.front();
        CanRelax.pop();
        inq[Now.Point][Now.Gcd] = false;

        for (int i = head[Now.Point]; ~i; i = Edge[i].nxt)
        {
            int Nowto = Edge[i].to;
            int Noww = Edge[i].w;

            if(Noww % std::__gcd(Now.Gcd, Noww))
                continue;

            if (dp[Now.Point][Now.Gcd] + Noww / std::__gcd(Now.Gcd, Noww) < dp[Nowto][std::__gcd(Now.Gcd, Noww)])
            {
                dp[Nowto][std::__gcd(Now.Gcd, Noww)] = dp[Now.Point][Now.Gcd] + Noww / std::__gcd(Now.Gcd, Noww);
                if (!inq[Nowto][std::__gcd(Now.Gcd, Noww)])
                {
                    inq[Nowto][std::__gcd(Now.Gcd, Noww)] = true;
                    CanRelax.push((node){Nowto, std::__gcd(Now.Gcd, Noww)});
                }
            }
        }
    }
}

signed main()
{

    scanf("%lld %lld", &n, &m);
    init();
    for (int i = 1; i <= m; i++)
    {
        int u, v, w;
        scanf("%lld %lld %lld", &u, &v, &w);
        addedge(u, v, w);
        addedge(v, u, w);
    }

    scanf("%lld", &Q);
    while(Q--)
    {
        int Begin;
        scanf("%lld", &Begin);
        spfa(Begin);
        int Ans = INF;
        for (int j = 1; j <= 500; j++)
        {
            Ans = std::min(Ans, dp[n][j]);
        }
        printf("%lld\n", Ans);
    }

    return 0;
}

/*
4 3
1 2 2
2 3 4
3 4 6
3
1
2
3

6
4
1
I
Love
You
*/

/*
10 20
3 4 34
1 5 97
4 1 85
7 8 81
6 1 23
8 3 57
5 2 77
9 1 68
10 3 95
2 10 68
8 5 21
6 8 68
5 7 34
2 8 91
2 7 37
3 7 68
2 9 68
8 4 68
5 10 68
2 8 68
7
1
7
4
6
3
9
5

3
3
3
3
1
2
1
*/

正解

#include <bits/stdc++.h>
#define int long long
const int MAXM = 2024;
const int MAXN = 1024;
const int INF = INT_MAX;

#define lcm(x, y) x / std::__gcd(x, y) * y

int n, m;

struct edge
{
    int to;
    int w;
    int nxt;
} Edge[MAXM << 1]; //
int head[MAXN];
int cnt = 0;

void init()
{
    for (int i = 1; i <= n; i++)
    {
        head[i] = -1;
    }
}

void addedge(int u, int v, int w)
{
    Edge[++cnt].to = v;
    Edge[cnt].w = w;
    Edge[cnt].nxt = head[u];
    head[u] = cnt;
}

int Q;

bool inq[MAXN][520];
int dp[MAXN][520]; //

struct node
{
    int Point;
    int Gcd;
};
std::queue<node> CanRelax;
int ans[MAXN]; // 存储每一个点的答案
void spfa()
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= 500; j++)
        {
            dp[i][j] = INF;
        }
    }
    memset(inq, false, sizeof(inq));
    for (int i = 1; i <= 500; i++)
    {
        dp[n][i] = 0;
        CanRelax.push((node){n, i});
        inq[n][i] = true;
    }
    for (int i = 1; i <= n; i++)
    {
        ans[i] = INF;
    }
    ans[n] = 0;

    while (!CanRelax.empty())
    {
        node Now = CanRelax.front();
        CanRelax.pop();
        inq[Now.Point][Now.Gcd] = false;
        ans[Now.Point] = std::min(ans[Now.Point], dp[Now.Point][Now.Gcd]);

        for (int i = head[Now.Point]; ~i; i = Edge[i].nxt)
        {
            int Nowto = Edge[i].to;
            int Noww = Edge[i].w;

            if (Noww % Now.Gcd || Nowto == n) // 这里记得防爆炸
                continue;

            for (int j = 1; j * Now.Gcd <= 500; j++)
            {
                if (dp[Nowto][j * Now.Gcd] > dp[Now.Point][Now.Gcd] + Noww / Now.Gcd) // 逆向顺序
                {
                    dp[Nowto][j * Now.Gcd] = dp[Now.Point][Now.Gcd] + Noww / Now.Gcd;
                    if (!inq[Nowto][j * Now.Gcd])
                    {
                        inq[Nowto][j * Now.Gcd] = true;
                        CanRelax.push((node){Nowto, j * Now.Gcd});
                    }
                }
            }
        }
    }
}

signed main()
{

    scanf("%lld %lld", &n, &m);
    init();
    for (int i = 1; i <= m; i++)
    {
        int u, v, w;
        scanf("%lld %lld %lld", &u, &v, &w);
        addedge(u, v, w);
        addedge(v, u, w);
    }

    scanf("%lld", &Q);
    spfa();
    while (Q--)
    {
        int Begin;
        scanf("%lld", &Begin);
        printf("%lld\n", ans[Begin]);
    }

    return 0;
}

/*
4 3
1 2 2
2 3 4
3 4 6
3
1
2
3

6
4
1
I
Love
You
*/

/*
10 20
3 4 34
1 5 97
4 1 85
7 8 81
6 1 23
8 3 57
5 2 77
9 1 68
10 3 95
2 10 68
8 5 21
6 8 68
5 7 34
2 8 91
2 7 37
3 7 68
2 9 68
8 4 68
5 10 68
2 8 68
7
1
7
4
6
3
9
5

3
3
3
3
1
2
1
*/

感觉比较神奇的一个题目, 复习一下
首先不难发现直接转移是可做的图上 \(\rm{dp}\), 使用 \(\rm{spfa}\) 收敛状态

考虑最终终点一定, 可能需要利用这一点
发现如果可以从终点倒推, 可能可以减少很多复杂度

但是发现因为边权需要起点的信息, 那不炸了吗
注意到 \(n, w\) 都在很小的量级上, 不妨对每个点的每种可能的 \(\gcd\) 都做处理, 这个是简单的, 一个类似分层图的处理

具体的, 想从 \(u, g_1\) 通过边权为 \(w\) 的边转移到 \(v, g_2\), 必定满足 \(g_2 \mid g_1, w\), 至于如果 \(\gcd(g_1, w) > g_2\), 错解不优
于是直接逆向转移所有 \(g_2 \mid g_1, w\) 对应的状态即可

不难发现最初的 \(\gcd\) 等于出边权值时最优, 因为错解不优, 最后直接全部统计即可

总结

一类套路题
之前居然没见过

有依赖性的 dp 必须吧每一维都开好
多观察题面

终点 / 起点 一定的题目都可以有特殊优化

小知识 \(\gcd{(a, b)} \mid a, b\)

往往考虑 \(\gcd\) 的倍数
错解不优的思想, 一般是要求最小化但是错解变大, 或者最大化但是错解变小


这题其实很有故事, 注意看样例

posted @ 2024-10-10 11:51  Yorg  阅读(17)  评论(0)    收藏  举报