最小移动距离
最小移动距离
平面上有 个点,编号为 。
对于每个点 (),都存在一条从点 到点 (, 可以等于 )的有向边。
所有边的长度均为 。
请你判断是否存在一个最小移动距离 (),使得:
- 我们规定,如果从点 出发,移动 单位长度距离后,到达点 ,就称点 是点 的目标点。注意,一个点的目标点也可能是它自己。
- 对于图中的每个点 ,如果点 是点 的目标点,则点 也必须是点 的目标点。
如果存在这样的 ,请你输出 的最小可能值,否则请你输出 。
输入格式
第一行包含一个整数 。
第二行包含 个整数 。
输出格式
如果存在满足条件的 (),则输出一个正整数,表示 的最小可能值。
否则输出 。
数据范围
前 个测试点满足 。
所有测试点满足 ,。
输入样例1:
4 2 3 1 4
输出样例1:
3
输入样例2:
4 4 4 4 4
输出样例2:
-1
输入样例3:
4 2 1 4 3
输出样例3:
1
解题思路
首先可以发现每个点的出度都是,意味着这个图是一个基环树,即一个环上挂着一堆树(也可能没有挂树,意味着此时是一个环图)。如果这个基环树挂着树,对于树中的任意一个点,它的目标结点一定会在树的父节点方向上或者在环上,但目标结点不可能在回到这个出发点,因为不存在往回走的路径。因此如果环上挂了树的话那么一定是无解。
比如红色这个点,它是树上的一个结点。从红色点出发,如果目标结点是树上的结点,由于目标结点没有往回走的路径,因此无法到达红色点。如果目标结点在环上,由于在环上的点无论这么走都只会到达环上的点,因此也无法到达红色的点。
因此如果想有解意味着必然是一堆环,环上不能挂着树。一个环上如果挂着树,意味着环上必然存在某个点的入度大于,因此如果环上挂着树等价于某个点的入度大于,即如果每一棵基环树上都没有挂树等价于所有点的入度都是。
如果所有的连通块都是环的话必然存在解,我们可以让等于所有环的长度的最小公倍数,这样的话对于环上的每一个点来说走步一定可以走回到自己,但这不是最优解。考虑环长度为偶数的情况,对于环上的两个点和,如果从走步可以走到,意味着从走步也可以走到,因此对于长度为偶数的环不一定要取整个环的长度,只要取长度的一半就可以了。如果环的长度是奇数那么就取整个环的长度。
求环的长度的话,由于每个环都是一个连通块,因此可以用并查集来求每个连通块的点数,由于连通块是一个环,因此连通块的点数就是环的长度。虽然这是一个有向图,但连通块是一个简单环,求环长度等价于求连通块的长度,因此可以用并查集。
还要考虑的一个问题是求最小公倍数会不会爆long long。假设把拆分成,的最小公倍数。因此问题就变成了把分成若干个数的和,使得这若干个数的乘积最大。这是个小学数奥问题,就是分尽可能多的,最后如果不够的话就分。由于最大取,因此可以分个和个,可以发现是不会爆long long的。
证明如下:
这道题目是数学中一个很经典的问题。
下面我们给出证明:首先把一个正整数 拆分成若干正整数只有有限种拆法,所以存在最大乘积。
假设 ,并且 是最大乘积。显然 不会出现在其中;
如果对于某 有 ,那么把 拆分成 ,我们有 ;
如果 ,拆成 乘积不变,所以不妨假设没有 ;
如果有三个以上的 ,那么 ,所以替换成3乘积更大;
综上,选用尽量多的 ,直到剩下 或者 时,用 。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 6 const int N = 110; 7 8 int d[N], fa[N], cnt[N]; 9 10 int find(int x) { 11 return fa[x] == x ? fa[x] : fa[x] = find(fa[x]); 12 } 13 14 LL gcd(LL a, LL b) { 15 return b ? gcd(b, a % b) : a; 16 } 17 18 int main() { 19 int n; 20 cin >> n; 21 for (int i = 1; i <= n; i++) { 22 fa[i] = i; 23 cnt[i] = 1; 24 } 25 for (int i = 1; i <= n; i++) { 26 int x; 27 scanf("%d", &x); 28 d[x]++; 29 int a = find(i), b = find(x); 30 if (a != b) { 31 cnt[b] += cnt[a]; 32 fa[a] = b; 33 } 34 } 35 36 for (int i = 1; i <= n; i++) { 37 if (d[i] > 1) { // 某个基环树环上挂着树 38 printf("-1"); 39 return 0; 40 } 41 } 42 43 LL ret = 1; 44 for (int i = 1; i <= n; i++) { 45 if (fa[i] == i) { 46 int t = cnt[i]; 47 if (t % 2 == 0) t >>= 1; // 如果环的长度是偶数则取长度的一半 48 ret = ret / gcd(ret, t) * t; 49 } 50 } 51 printf("%lld", ret); 52 53 return 0; 54 }
参考资料
AcWing 4626. 最小移动距离(AcWing杯 - 周赛):https://www.acwing.com/video/4461/
LeetCode 343. Integer Break:https://www.acwing.com/solution/content/368/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16771113.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效