题目链接:点击查看
题目大意:首先给出一个 1 ~ n 的排列,用 p 数组表示,再给出给个点的颜色,用 c 数组表示
然后抛出Infinite Path的定义:对于某个 i ,p[ i ] , p[ p[ i ] ] , p[ p[ p[ i ] ] ] 无限嵌套下去,且每个位置的颜色相同,即 c[ i ] = c[ p[ i ] ] = c[ p[ p[ i ] ] ] = c[ p[ p[ p[ i ] ] ] ] 等等等等
下面重载一下排列的乘法运算,假如 a 和 b 是 1 ~ n 的两个排列,那么 a * b = b[ a[ i ] ] ,a * a = a[ a [ i ] ]
从而重载一下排列的乘方运算,p^k = p * p * p ... * p ( k 个 p )
题目要求我们找到一个最小的 k ,使得给出的排列 p 的 k 次方,存在一个 i ,可以满足上述的Infinite Path
题目分析:读完题后,不难想到如果想要找到Infinite Path的话,必然是一个循环,而 1 ~ n 的一个排列,如果转换为有向图的形式,可以视为数个不相交的有向环,之前做题记住的结论,忘记是怎么证明的了,这样就可以将给出的排列 p 以有向图的形式表示,接下来结合前几周离散数学新学到的知识(学以致用)
可以将 p^k 这个抽象的概念具体化,简单来说,p^k 就是在 p 这个有向图中,经过长度为 k 的步长可以到达的关系
再换句话说,p^k 就是在环中将距离相隔为 k 的数个点按照原次序重新组合成一个新的环
如果上面这句话不好理解的话,对于题目给出的第三个样例画一下图就好理解了
综上所述,现在题目就转换为了,在数列 p 的所有环中,能否找到一个等差 k ,使得环上所有满足等差关系的位置:pos , pos + k , pos + 2 * k , pos + 3 * k 等等位置的颜色相同,维护这个最小的 k 就是答案了
到此为止这个题目剩下的就是实现了,比赛的时候明明已经到了这一步,却卡在了如何在环上找到等差的位置这里,不会实现,还是自己太菜了啊,赛后看了大佬的代码发现只需要nlogn维护因子就好了,因为如果想满足在环上满足等差,那么这个差值 k 一定是环的长度的因子,对于每个长度的因子,nlogn预处理一下就可以了
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=2e5+100;
int p[N],c[N];
bool vis[N];
vector<int>d[N];
int cal(vector<int>loop)
{
int mmin=inf;
for(auto len:d[loop.size()])//枚举每个因子作为差值 k
{
for(int i=0;i<len;i++)//枚举起点
{
bool flag=true;
for(int j=i;j<loop.size();j+=len)
{
if(c[loop[i]]!=c[loop[j]])
{
flag=false;
break;
}
}
if(flag)
mmin=min(mmin,len);
}
}
return mmin;
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
for(int i=1;i<N;i++)//预处理因子
for(int j=i;j<N;j+=i)
d[j].push_back(i);
int w;
cin>>w;
while(w--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",p+i);
for(int i=1;i<=n;i++)
scanf("%d",c+i);
for(int i=1;i<=n;i++)
vis[i]=false;
int ans=n;
for(int i=1;i<=n;i++)
{
if(vis[i])
continue;
int k=i;
vector<int>loop;//找环
while(!vis[k])
{
loop.push_back(k);
vis[k]=true;
k=p[k];
}
ans=min(ans,cal(loop));
}
printf("%d\n",ans);
}
return 0;
}