题意是给一个字符串s和一个与之相同长度的数组排列a,字符串s按照数组给定的位置进行变化,新字符串的第i个字符是上一个字符串的第a[i]个字符
比如 例子
5
ababa
2 1 4 5 3
变化6次
babaa
abaab
baaba
abbaa
baaab
ababa
变回原来的字符串,问需要最少几次变化为原来的字符串
第一个点,观察发现,字符串变化时一些位置的字符变化时循环的,比如上例的ababa的前两个字符ab,因为a[1]=2,a[2]=1,第一个字符到第二个字符,第二个字符到第一个字符,如此一直循环。因此可以建图,从结点 i 连一条边到结点 a[i] ,可以得出一个结论,所有这种图上的环都可以到变回到原来的样子,这是第一个点,应该不难发现
第二个点,这种环需要几次变化才能回到原来状态呢,开始我想当然以为如果环上字符都相等是一次,否则为环上结点的个数,直接wa了,后来想了一下应该是字符串的子字符串循环的最小长度
比如abcabc是3,因为变化3次为bcabca,cabcab,abcabc
第三个点,知道了每个环上变换的次数,那么总次数是多少呢,我又想当然的以为是全部乘起来,然后又wa了,其实想了一下应该是每个环的变换次数的最小公倍数(我好笨......),因为最小公倍数能保证每个环的循环的次数的倍数,让其回到原来的状态,所以写个lcm就可以过了
一个1700的比较综合的题目,记录一下,wa了两发才过,刚放暑假好久没写代码手都生了,看来还得再仔细一点
以下为AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
char s[210];
int g[210];
bool used[210];
ll gcd(ll a,ll b)
{
return b?gcd(b,a%b):a;
}
ll lcm(ll a,ll b)
{
return a*b/gcd(a,b);
}//求最小公倍数
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++)
{
used[i]=false;
int k;
cin>>k;
g[i]=k;
}
ll ans=1;
for(int i=1;i<=n;i++)
{
if(!used[i])
{
int nodenum=0;
string tmp="";
int loc=i;
while(!used[loc])//把环上字符连成串
{
used[loc]=true;
tmp+=s[loc];
loc=g[loc];
}
int sl=tmp.length();
for(int i=1;i<=sl;i++)//求变化最小次数
{
if(sl%i==0)
{
bool flag=true;
for(int j=0;j<i;j++)
{
char pre=tmp[j];
for(int k=j;k<sl;k+=i)
{
if(pre!=tmp[k])
{
flag=false;
break;
}
}
if(!flag) break;
}
if(flag)
{
nodenum=i;
break;
}
}
}
ans=lcm(ans,(ll)nodenum);
}
}
cout<<ans<<"\n";
}
return 0;
}