D. Friendly Spiders
D. Friendly Spiders
Mars is home to an unusual species of spiders — Binary spiders.
Right now, Martian scientists are observing a colony of spiders, the -th of which has legs.
Some of the spiders are friends with each other. Namely, the -th and -th spiders are friends if , i. e., there is some integer such that and are simultaneously divided by without a remainder. Here denotes the greatest common divisor (GCD) of integers and .
Scientists have discovered that spiders can send messages. If two spiders are friends, then they can transmit a message directly in one second. Otherwise, the spider must pass the message to his friend, who in turn must pass the message to his friend, and so on until the message reaches the recipient.
Let's look at an example.
Suppose a spider with eight legs wants to send a message to a spider with legs. He can't do it directly, because . But he can send a message through the spider with six legs because and . Thus, the message will arrive in two seconds.
Right now, scientists are observing how the -th spider wants to send a message to the -th spider. The researchers have a hypothesis that spiders always transmit messages optimally. For this reason, scientists would need a program that could calculate the minimum time to send a message and also deduce one of the optimal routes.
Input
The first line of input contains an integer () — the number of spiders in the colony.
The second line of input contains integers () — the number of legs the spiders have.
The third line of input contains two integers and () —the spiders between which the message must be sent.
Output
If it is impossible to transmit a message between the given pair of spiders, print .
Otherwise, in the first line of the output print the integer () — the number of spiders that participate in the message transmission (i. e. the minimum time of message delivery in seconds plus one). In the second line, print different integers () — the ids of the spiders through which the message should follow, in order from sender to receiver.
If there are several optimal routes for the message, output any of them.
Examples
input
7 2 14 9 6 8 15 11 5 6
output
3 5 4 6
input
7 2 14 9 6 8 15 11 5 7
output
-1
input
7 2 14 9 6 8 15 11 5 5
output
1 5
Note
The first example is shown above. It shows that the message from the -th spider (with eight legs) to the -th spider (with legs) is optimal to pass through the -th spider (with six legs).
In the second example, the spider number (with legs) is not friends with anyone, so it is impossible to send him a message.
解题思路
题意大概就是如果两个数的最大公约数不为,那么就在这两个数间连一条边。如果直接按照定义来建图那么时间复杂度和空间复杂度都是,必然超时。突破口是在值域上,,意味着我们可以尝试从值域的角度去建图。实际上官方给出的建图方法还是很难想到的(反正我是不可能想到的),看完后只能用妙来形容。
创建一个二分图,其中左边部分由个构成,右边部分由所有不超过(或者不超过)的质数构成。遍历左边部分的每一个点,对于左边部分的某一个点对其进行质因数分解,在右边部分找到所有的质因子并各自与连一条边。
下面分析时间复杂度,假设,建图的流程是先筛出所有不超过的质数,这部分的时间复杂度为。
然后是分解质因数,如果直接暴力分解质因数那么时间复杂度为,最大可以达到级别,很可能会超时。因此在筛质数的过程中顺便求出每个数的最小质因子,在分解质因数的过程中就可以根据最小质因子来试除,时间复杂度就变成了。
然后试根据质因子来连边,首先因为有,所有在不超过的数中,一个数最多有个不同的质因子,因此建边的时间复杂度为,空间复杂度为。
最后是点数的问题,二分图左边部分有个点,右边部分的节点个数取决于不超过的质数的个数,因此总的节点个数最多不会超过个(实际上不超过的质数有个),其中右边部分的节点编号从开始。
建完图然后就是求最短路了,根据题意可设所有边的权值均为,因此可以跑个,时间复杂度为。最后答案就是从起点到终点距离的一半,即答案要除以(实际上就是减去所有从右边部分到左边部分的边的权值)。当然也可设从左到右的边的权值为,从右到左的边的权值为,然后跑个。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 3e5 + 10, M = 6e5 + 10, K = 36e5 + 10; 5 6 int n, src, dest; 7 int a[N]; 8 int head[M], e[K], ne[K], idx; 9 int primes[N], minp[N], cnt; 10 bool vis[N]; 11 int id[N]; 12 int q[M], hh, tt = -1; 13 int dist[M], path[M]; 14 15 void add(int v, int w) { 16 e[idx] = w, ne[idx] = head[v], head[v] = idx++; 17 } 18 19 void get_prime(int m) { 20 for (int i = 2; i <= m; i++) { 21 if (!vis[i]) { 22 primes[cnt++] = i; 23 minp[i] = i; 24 id[i] = n + cnt; // 右边部分的节点编号从n+1开始 25 } 26 for (int j = 0; primes[j] <= m / i; j++) { 27 vis[primes[j] * i] = true; 28 minp[primes[j] * i] = primes[j]; 29 if (i % primes[j] == 0) break; 30 } 31 } 32 } 33 34 bool bfs() { 35 if (src == dest) return true; 36 q[++tt] = src; 37 memset(dist, 0x3f, sizeof(dist)); 38 dist[src] = 0; 39 while (hh <= tt) { 40 int t = q[hh++]; 41 for (int i = head[t]; i != -1; i = ne[i]) { 42 if (dist[e[i]] > dist[t] + 1) { 43 dist[e[i]] = dist[t] + 1; 44 path[e[i]] = t; 45 if (e[i] == dest) return true; 46 q[++tt] = e[i]; 47 } 48 } 49 } 50 return false; 51 } 52 53 int main() { 54 scanf("%d", &n); 55 for (int i = 1; i <= n; i++) { 56 scanf("%d", a + i); 57 } 58 get_prime(*max_element(a + 1, a + n + 1)); // 筛出不超过max(a[1~n])的质数 59 memset(head, -1, sizeof(head)); 60 for (int i = 1; i <= n; i++) { 61 int x = a[i]; 62 while (x > 1) { // 分解质因数 63 int t = minp[x]; 64 add(i, id[t]), add(id[t], i); // 右边部分的质因子t与a[i]连一条无向边 65 while (x % t == 0) { 66 x /= t; 67 } 68 } 69 } 70 scanf("%d %d", &src, &dest); 71 if (bfs()) { // 有解 72 vector<int> stk; 73 while (dest) { // 求最短路径的具体方案 74 if (dest <= n) stk.push_back(dest); // 路径中不应该包含右边部分的点 75 dest = path[dest]; 76 } 77 printf("%d\n", stk.size()); 78 reverse(stk.begin(), stk.end()); 79 for (auto &x : stk) { 80 printf("%d ", x); 81 } 82 } 83 else { // 无解 84 printf("-1"); 85 } 86 87 return 0; 88 }
一点总结。
如果发现有个点可以两两互相到达,比如所有的倍数,如果每两个点都建一条权值为的边的话时间复杂度和空间复杂度都是。这题就给出了一种新的建图方式,可以开个虚点,其他点到虚点的边的权值为,从虚点到其他点的权值为,一样可以达到同样的效果,而时间复杂度和空间复杂度都变成了,如下图:
其中这里的虚点因为这个点的共同关系而把这个点连接起来,在这道题这个点的共同关系就是都有同一个质因数,而虚点就是二分图右边部分的质数。
更新:参考Minimum Reverse Operations所带来的启发,这里再提供另外一种做法。
在bfs时最重要的问题是每个节点会与大量的节点有边相连,因此在枚举所有的边时会导致超时。而根据bfs的性质,如果一个节点已经被更新了,那么到这个节点的最小距离就已经确定了,之后不会再被更新。因此在枚举相邻节点时很多节点都是无用节点(不会再被更新)。
为此这里参考这道题目的做法,先给每一个质数开一个哈希表,用来存储含有该质因子的数所对应的下标。那么在bfs时要枚举数能到达的相邻节点时,先对进行质因数分解,然后对于每个不同的质因子在对应的哈希表中存在的节点进行更新,并将更新过的节点从所有哈希表中删除。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 3e5 + 10; 5 6 int n, src, dest; 7 int a[N]; 8 int primes[N], cnt; 9 bool vis[N]; 10 int minp[N]; 11 unordered_set<int> st[N]; 12 vector<int> fs[N]; 13 int dist[N], path[N]; 14 int q[N], hh, tt = -1; 15 16 void get_prime(int n) { 17 for (int i = 2; i <= n; i++) { 18 if (!vis[i]) { 19 primes[cnt++] = i; 20 minp[i] = i; 21 } 22 for (int j = 0; primes[j] <= n / i; j++) { 23 vis[primes[j] * i] = true; 24 minp[primes[j] * i] = primes[j]; 25 if (i % primes[j] == 0) break; 26 } 27 } 28 } 29 30 int main() { 31 int n; 32 scanf("%d", &n); 33 for (int i = 1; i <= n; i++) { 34 scanf("%d", a + i); 35 } 36 scanf("%d %d", &src, &dest); 37 get_prime(*max_element(a + 1, a + n + 1)); 38 for (int i = 1; i <= n; i++) { 39 int t = a[i]; 40 while (t != 1) { 41 int p = minp[t]; 42 if (i != src) st[p].insert(i); 43 fs[i].push_back(p); 44 while (t % p == 0) { 45 t /= p; 46 } 47 } 48 } 49 memset(dist, 0x3f, sizeof(dist)); 50 dist[src] = 0; 51 q[++tt] = src; 52 while (hh <= tt) { 53 int t = q[hh++]; 54 for (auto &x : fs[t]) { 55 auto it = st[x].begin(); 56 while (it != st[x].end()) { 57 int c = *it; 58 dist[c] = dist[t] + 1; 59 path[c] = t; 60 q[++tt] = c; 61 auto p = next(it); 62 for (auto &x : fs[c]) { 63 st[x].erase(c); 64 } 65 it = p; 66 } 67 } 68 } 69 if (dist[dest] == 0x3f3f3f3f) { 70 printf("-1"); 71 } 72 else { 73 printf("%d\n", dist[dest] + 1); 74 vector<int> ans; 75 while (dest) { 76 ans.push_back(dest); 77 dest = path[dest]; 78 } 79 reverse(ans.begin(), ans.end()); 80 for (auto &x : ans) { 81 printf("%d ", x); 82 } 83 } 84 85 return 0; 86 }
参考资料
Codeforces Round #843 (Div. 2) Editorial:https://codeforces.com/blog/entry/111286
Codeforces Round #843 (Div. 2) D(数学+图论) E(结论):https://zhuanlan.zhihu.com/p/598085405
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17058463.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效