CodeForces1327 D. Infinite Path(将排列转化为有向环+枚举)

题面:

在这里插入图片描述

题意:

给长度为n的一个排列p,每个位置有一个颜色c
定义无限路径为:i,p(i),p(p(i)…,且c(i)=c(p(i))=c(p(p(i))),即颜色相同
定义两个排列a与b的乘积x=a*b为:x(i)=b(a(i))
现在要你求一个最小的非零k,满足排列pk存在一个点i,以i开始为一条无限路径(无限路径的定义见上文)

解法:

一个长度为n的排列,对每个i到p(i)建立一条有向边,
因为每个点入度和出度都为1,那么就会形成若干个连通块,每个连通块都是一个环。

例如排列:·1,3,4,2:
在这里插入图片描述
pk则为每个点沿着有向边走k步。
那么题目变为求一个最小的k,满足p上的某个点不断走k步,能绕回原来这个点且走到的点颜色都相同。

先求出每一个环,假设环的长度为len,枚举len的因子x作为步数,当x满足条件的时候,用x更新ans,取min即可

因为如果步数x的因子不是len的因子,那么gcd(x,len)=1,
这样的话从环上任意点开始不断走x步,最终会走遍环上的所有点,与x取1的效果是一样的。

如果步数x是len的因子,那么不会经过所有点,假设起点为st,st+k*x>len,即走k次x步之后会绕回去,
假设走k次x步,那么st+kx一定是之前走过的点,已经开始循环了,所以只需要判断到st+kx<=len就行了。

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e5+5;
vector<int>g[maxm];
int mark[maxm];
int p[maxm];
int c[maxm];
int ans;
int n;
void solve(int x){
    vector<int>s;//存环
    while(!mark[x]){
        s.push_back(x);
        mark[x]=1;
        x=p[x];
    }
    int len=s.size();
    for(int i=1;i<=len;i++){//枚举步数,步数一定是len的因子
        if(len%i==0){//i可以作为步数
            for(int j=0;j<i;j++){//枚举起点
                int ok=1;
                for(int k=j;k+i<len;k+=i){//判断起点开始每i步的点是否颜色相同
                    if(c[s[k]]!=c[s[k+i]]){
                        ok=0;
                        break;
                    }
                }
                if(ok){//更新答案并退出,因为只需要找最小的
                    ans=min(ans,i);
                    return ;
                }
            }
        }
    }
}
signed main(){
    int T;
    cin>>T;
    while(T--){
        cin>>n;
        ans=n;
        for(int i=1;i<=n;i++)cin>>p[i];
        for(int i=1;i<=n;i++)cin>>c[i];
        for(int i=1;i<=n;i++)mark[i]=0;
        for(int i=1;i<=n;i++)if(!mark[i])solve(i);
        cout<<ans<<endl;
    }
    return 0;
}

发布了445 篇原创文章 · 获赞 37 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_44178736/article/details/105180870
今日推荐