Magic
题目
由于没找到原题, 只能挂上 pdf
题目下载
算法
暴力
标准的 dp + 图论(可是考前并没有听说过)
于是推出式子
定义 \(dp_{u, g}\) 为从起点到 \(u\), 经过的边权的最大公倍数为 \(g\) 的最短路, 枚举 \(u\) 的出边 \(v\)
转移时显然只能从出边中找, 并且由于不是 DAG, 需要想办法收敛状态
于是想到 spfa 跑一遍 (不知道这种情况下 spfa 是死是活, 疑似有利用 dp 单调性的更优算法)
平均时间复杂度 \(O(qm\sqrt{m})\)
正解
挂个 pdf
题解下载
想到之前一个同学, 遂下定决心 A 了这道题
终点一定, 大概要倒着推 dp
问题在于边权实在不好表示
因为边权只与起点到这个点的 \(\gcd\) 有关
定义 \(dp_{u, g}\) 表示 \(u\) 到 \(n\) 点, 从起点到 \(u\) 的 \(\gcd\) 为 \(g\)
枚举 \(v\) 的出边 \(u\)
枚举 \(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\) 的倍数
错解不优的思想, 一般是要求最小化但是错解变大, 或者最大化但是错解变小
这题其实很有故事, 注意看样例

浙公网安备 33010602011771号