Magic

题目#

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

算法#

暴力#

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

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

dpv,g=minexist(uv)(dpu,g+wg)

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

正解#

挂个 pdf
题解下载

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

终点一定, 大概要倒着推 dp
问题在于边权实在不好表示
因为边权只与起点到这个点的 gcd 有关
定义 dpu,g 表示 un 点, 从起点到 ugcdg

枚举 v 的出边 u

dpu,g=minexist(vu)(dpu+wg)

枚举 gg 倍数即可转移

能把时间复杂度优化到平均 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
*/

总结#

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

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

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

小知识 gcd(a,b)a,b

胡扯#

感谢样例输出让我 A 了这题

posted @   Yorg  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示