acwing.第72场周赛 t3最小移动距离

AcWing 4626. 最小移动距离

原题链接:https://www.acwing.com/problem/content/4629/

思路

要求对于每一个点x都满足走过t,到达一个目标点y.并且x和y都可以互为目标点。
找出t的最小值。

每一个点的出度为1.易得,互为目标点的点必须在同一个环上才可以满足条件。

维护一个并查集(一个集合就是一个环),维护每个点的入度,维护每个并查集的大小(环的长度)

只看一个环的t:
如果一个环中点的个数为偶数个,那么答案可以是len/2
如果一个环中点的个数为奇数个,那么答案只能是len

n个点可以分为若干个环,最终的答案是这些个环的最小公倍数
此题需要考虑,n分为若干个数之后,这若干个数的最小公倍数会不会爆int,会不会爆long long(爆longlong要开高精度)

n = [a1,a2,...ak],要使得a1 * a2 * ... * ak最大,把n尽量分得更多的3,剩下的用2去凑,这样的使得积最大(结论).
本题n最大100,那分为32个3,2个2。最坏情况为3^32 * 4 =>9 ^ 16 * 4 小于 10 ^ 16 * 4,不会爆longlong

代码
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

typedef long long LL;
const int N = 110;

int d[N],p[N],s[N]; // 记录入度,并查集数组,环的长度
int n;

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]); // x的父亲结点置为祖宗结点
    return p[x]; 
}

LL gcd(LL a,LL b)
{
    return b ? gcd(b,a%b) : a;
}

LL get()
{
    // 检查所有入度是否都是1(是否都是环)
    for(int i = 1; i <= n; i++)
    {
        if(d[i] != 1) return -1;
    }
    
    // 计算答案 每个环的最小公倍数
    LL res = 1;
    for(int i = 1; i <= n;i ++)
    {
        if(p[i] == i) // 如果是环的起点
        {
            int len = s[i];
            if(len % 2 == 0) len /= 2;
            res = (res * len) / gcd(res,len);
        }
    }
    return res;
}

int main()
{
    cin >> n;
    
    // 并查集初始化
    for(int i = 1; i <= n; i ++)
    {
        p[i] = i;
        s[i] = 1;
    }
    
    // 并查集合并
    for(int i = 1; i <= n; i ++)
    {
        int x;
        cin >> x;
        d[x] ++; // x入度+1
        int a = find(i),b = find(x);
        if(a != b) // 如果没有在一个集合里就放进
        {
            p[a] = b;
            s[b] = s[b] + s[a];
        }
    }
    
    cout << get() << endl;
    
    return 0;
}
posted @ 2022-10-10 11:38  r涤生  阅读(22)  评论(0编辑  收藏  举报