最小移动距离

最小移动距离

平面上有 n 个点,编号为 1n

对于每个点 i1in),都存在一条从点 i 到点 ai1ainai 可以等于 i)的有向边。

所有边的长度均为 1

请你判断是否存在一个最小移动距离 tt1),使得:

  • 我们规定,如果从点 u 出发,移动 t 单位长度距离后,到达点 v,就称点 v 是点 u 的目标点。注意,一个点的目标点也可能是它自己。
  • 对于图中的每个点 x,如果点 y 是点 x 的目标点,则点 x 也必须是点 y 的目标点。

如果存在这样的 t,请你输出 t 的最小可能值,否则请你输出 1

输入格式

第一行包含一个整数 n

第二行包含 n 个整数 a1,a2,,an

输出格式

如果存在满足条件的 tt1),则输出一个正整数,表示 t 的最小可能值。

否则输出 1

数据范围

3 个测试点满足 1n4
所有测试点满足 1n1001ain

输入样例1:

4
2 3 1 4

输出样例1:

3

输入样例2:

4
4 4 4 4

输出样例2:

-1

输入样例3:

4
2 1 4 3

输出样例3:

1

 

解题思路

  首先可以发现每个点的出度都是1,意味着这个图是一个基环树,即一个环上挂着一堆树(也可能没有挂树,意味着此时是一个环图)。如果这个基环树挂着树,对于树中的任意一个点,它的目标结点一定会在树的父节点方向上或者在环上,但目标结点不可能在回到这个出发点,因为不存在往回走的路径。因此如果环上挂了树的话那么一定是无解。

  比如红色这个点,它是树上的一个结点。从红色点出发,如果目标结点是树上的结点,由于目标结点没有往回走的路径,因此无法到达红色点。如果目标结点在环上,由于在环上的点无论这么走都只会到达环上的点,因此也无法到达红色的点。

  因此如果想有解意味着必然是一堆环,环上不能挂着树。一个环上如果挂着树,意味着环上必然存在某个点的入度大于1,因此如果环上挂着树等价于某个点的入度大于1,即如果每一棵基环树上都没有挂树等价于所有点的入度都是1

  如果所有的连通块都是环的话必然存在解,我们可以让t等于所有环的长度的最小公倍数,这样的话对于环上的每一个点来说走t步一定可以走回到自己,但这不是最优解。考虑环长度为偶数的情况,对于环上的两个点xy,如果从xt2步可以走到y,意味着从yt2步也可以走到x,因此对于长度为偶数的环不一定要取整个环的长度t,只要取长度的一半就可以了。如果环的长度是奇数那么就取整个环的长度。

  求环的长度的话,由于每个环都是一个连通块,因此可以用并查集来求每个连通块的点数,由于连通块是一个环,因此连通块的点数就是环的长度。虽然这是一个有向图,但连通块是一个简单环,求环长度等价于求连通块的长度,因此可以用并查集。

  还要考虑的一个问题是求最小公倍数会不会爆long long。假设把n拆分成n=a1+a2++aka1ak的最小公倍数[a1,a2,,ak]i=1kai。因此问题就变成了把n分成若干个数的和,使得这若干个数的乘积最大。这是个小学数奥问题,就是分尽可能多的3,最后如果不够3的话就分2。由于n最大取100,因此可以分32322,可以发现332×22是不会爆long long的。

  证明如下:

这道题目是数学中一个很经典的问题。
下面我们给出证明:

首先把一个正整数 N 拆分成若干正整数只有有限种拆法,所以存在最大乘积。
假设 N=n1+n2++nk,并且 n1×n2××nk是最大乘积。

显然 1 不会出现在其中;
如果对于某 ini5,那么把 ni 拆分成 3+(ni3),我们有 3(ni3)=3ni9>ni
如果 ni=4,拆成 2+2 乘积不变,所以不妨假设没有 4
如果有三个以上的 2,那么 3×3>2×2×2,所以替换成3乘积更大;
综上,选用尽量多的 3,直到剩下 2 或者 4 时,用 2

  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/

posted @   onlyblues  阅读(151)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示