原题链接:Problem - D - Codeforces
题目大意:
有 个蜘蛛,每个蜘蛛有 条腿,当 时候,这两个蜘蛛是朋友,可以互相交流。已知他们交流一次需要一秒钟, 给出你两个蜘蛛的位置,让你求出第 个蜘蛛与第 只蜘蛛交流所需要的时间,或者说这是不可能的。
解题思路:
两只蜘蛛相互交流,当且仅当他们不互质。那么可以考虑他们的最小质因子,发现,如果两个数存在某一个质因子相同,那么就可以建边相连,然后跑最短路就行了。
但是你会发现,这样是不可行的,因为最坏的情况下,你几乎要建 条边,这个复杂度是不可接受的。那么我们要如何优化建图呢?
考虑最小质因子,如果两个数存在某个质因子相同,那就可以相连。推广结论,如果多个数都存在某个质因子相同,那么说明这些点都是互通的,建多条边显然很浪费。
那么,我们可以考虑建一个虚拟源点来优化一下我们的图。
对于 来说,它们都有相同的最小质因子,也就是 。如果不考虑虚拟源点的话,建出的图应该是这样的。
事实上,他们都是互通的,任意点都能走向另一个点。那么我们是否可以让他们都连上一个虚拟的点,优化一下他们的路径呢?显然是可以的。
我们创建一个无穷大的点,来代表这个点。然后将每个点都连上这个点,我们的图会变成这样。
这样,我们任意两点之间仍然是互通的,我们仍然可以从一个点直接到另一个点,但是我们的边数得到了极大的优化!
你会发现,边数从直接下降到了 的级别,这样做可行吗?可行!题目中数据范围只有 的级别,也就是说,我们的虚拟源点是可以通过建在范围之外,而不影响范围之内原有的图的。
那么最短路怎么办呢?如果这样建图,那不是路径会变长了吗?
没关系,这样建图完全不影响。这里有两种方法,一种是01-BFS,另一种是不考虑距离的做法。
01-BFS的做法是利用双端队列,每次遇到范围大于 的点,就不让它的距离加 1 ,保持原来的距离,更新并放到队头,而范围小于的点加 1 后直接放到队尾,这样我们的距离就不会因为这个虚拟源点而变大。很容易证明这样跑出来的最短路是正确的。
本蒟蒻采用的是后者的做法。事实上我们不用考虑每个点的距离,我们只需要看最后我们还原最短的路径时,统计范围小于 的点有多少个,筛除大于这个范围的虚拟源点,剩下的就是我们所需要的正确点数,那么最短路径也就是点数的个数了。
题目还存在不可能有解的情况,那么我们只需要跑一次最短路,如果终点的距离还是无穷大的话,那么说明两个蜘蛛之间就不能联系,此时输出 -1 就好了。
代码分段解释:
首先先利用线性筛筛出每个数的最小质因子。
const int N = 3e5 + 10;
vector<int> primes;//存范围内的每个质数
int minp[N];//存每个数的最小质因子
void getprimes()
{
//范围内大概有这么多的质数,预先处理一下空间
primes.reserve(26000);
//线性筛最小质因子板子
for (int i = 2; i <= N; ++i)
{
if (!minp[i]) minp[i] = i, primes.emplace_back(i);
for (auto& p : primes)
{
if (i * p > N) break;
minp[i * p] = p;
if (i % p == 0) break;
}
}
}
然后对就是对虚拟源点建边的处理了 。要让虚拟的点不影响到原图,只需要把他扩展到范围外就行了,所以我们对每个最小质因子加上一个 ,让每个包含这个质因子的蜘蛛都向它建边,这样我们的建图复杂度就得到了优化。
const int N = 3e5 + 10;
//由于存在虚拟源点 所以我们的 N 要开两倍
vector<int> g[N << 1];//vector邻接表存图
vector<int> arr;
int st, ed;
void solve()
{
getprimes();//先筛质因子
int n;
cin >> n;
arr.resize(n + 1);//扩一下空间
for (int i = 1; i < arr.size(); ++i)
cin >> arr[i];
cin >> st >> ed;//st是起点 ed是终点
for (int i = 1; i < arr.size(); ++i)//枚举每一个蜘蛛建边
for(int t = arr[i]; t > 1; t /= minp[t])
{
//把每个数的最小的质因子都加上N之后大于3e5的范围 就不会影响原图了
//可以用map来记录边,防止重边,但是没必要
g[i].emplace_back(minp[t] + N);
g[minp[t] + N].emplace_back(i);
}
}
然后是跑普通bfs最短路的代码。
//g是图 dist是距离 由于包括虚拟源点 所以我们 N 要开两倍
vector<int> g[N << 1], dist(N << 1, 0x3f3f3f3f);
//path用来记录每一个点是从哪个点转移来的
int path[N << 1], st, ed;
void bfs()//bfs板子
{
queue<int> que;
que.push(st);
dist[st] = 0;
while (que.size())
{
auto t = que.front(); que.pop();
for (auto& x : g[t])
{
//如果dist被更新的话跳过
if (dist[x] != 0x3f3f3f3f) continue;
dist[x] = dist[t] + 1;
//让下一个点的前驱变成自己
path[x] = t;
que.push(x);
}
}
}
这样,我们就完成了所有步骤,只需要还原答案的路径就行了。
void solve()
{
bfs();//跑最短路
//判断一下,如果终点不能到达,就输出 -1
if (dist[ed] == 0x3f3f3f3f)
cout << -1 << '\n';
else
{
//我们用ans来还原路径
vector<int> ans;
//从ed 也就是终点开始 一直寻找前驱 如果在范围内就加入路径里
for (int i = ed; i; i = path[i])
if (i < N) ans.push_back(i);
//ans里剩下的点就是原图的点了
//由于我们是从终点返回找路径的 接下来只需要反向输出ans就行
cout << ans.size() << '\n';
for (int i = ans.size() - 1; ~i; --i)
cout << ans[i] << ' ';
}
}
至此,这题就解决了。下面是完整代码。
AC代码:
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const int N = 3e5 + 10;
vector<int> primes;
int minp[N];
int path[N << 1], st, ed;
vector<int> g[N << 1], dist(N << 1, 0x3f3f3f3f);
vector<int> arr;
void getprimes()
{
primes.reserve(26000);
for (int i = 2; i <= N; ++i)
{
if (!minp[i]) minp[i] = i, primes.emplace_back(i);
for (auto& p : primes)
{
if (i * p > N) break;
minp[i * p] = p;
if (i % p == 0) break;
}
}
}
void bfs()
{
queue<int> que;
que.push(st);
dist[st] = 0;
while (que.size())
{
auto t = que.front(); que.pop();
for (auto& x : g[t])
{
if (dist[x] != 0x3f3f3f3f) continue;
dist[x] = dist[t] + 1;
path[x] = t;
que.push(x);
}
}
}
void solve()
{
getprimes();
int n; cin >> n;
arr.resize(n + 1);
for (int i = 1; i < arr.size(); ++i)
cin >> arr[i];
cin >> st >> ed;
for (int i = 1; i < arr.size(); ++i)
for(int t = arr[i]; t > 1; t /= minp[t])
{
g[i].emplace_back(minp[t] + N);
g[minp[t] + N].emplace_back(i);
}
bfs();
if (dist[ed] == 0x3f3f3f3f)
cout << -1 << '\n';
else
{
vector<int> ans;
for (int i = ed; i; i = path[i])
if (i < N) ans.push_back(i);
cout << ans.size() << '\n';
for (int i = ans.size() - 1; ~i; --i)
cout << ans[i] << ' ';
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("r.in", "r", stdin);
//freopen("w.out", "w", stdout);
#endif
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//int t; cin >> t;
//while (t--) solve();
solve();
return 0;
}