题面:
题意:
给长度为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;
}