题意
给定一个排列,这个排列是由1~n的一个排列经过如下两步变换得到的
1:将整个序列向右移动任意次,1位会被移动到2位。。。n位会被移动到1位
2:选择任意两个数交换(不超过n/3个数字)
问得到题目中所给的排列则可能向右移动了多少次,输出结果
思路
因为选择两个数字交换的操作至多只会进行n/3次。
所以至少有n/3的数字保留在原地,就是说和1~n的序列比较之后,相差距离相同的数字的数量至少有1/3。
所以可能向右移动的次数至多有三种
可以先算出来相差的步数,之后对于每一种情况。分别判断剩下的能否用1/3的次数来移动成功,可以使用并查集来判断。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=300005;
int n,m,T;
int a[N],tmp[N],ans[N],cnt;
int fa[N],anss[N],tot;
int find(int x)
{
if (x==fa[x]) return x;
return fa[x]=find(fa[x]);
}
void check(int x)
{
for (int i=1; i<=n; i++)
fa[i]=i;
int res=0;
for (int i=1; i<=n; i++)
{
int u=i-x;
if (u<=0) u+=n;
int x1=find(u),y1=find(a[i]);
if (x1==y1)
res++;
else
fa[x1]=y1;
}
if (m>=n-res) anss[++tot]=x;
return;
}
int main()
{
int t;
cin>>t;
while(t--)
{
cin>>n>>m;
for (int i=1; i<=n; i++)
cin>>a[i];
for (int i=0; i<=n; i++)
tmp[i]=ans[i]=anss[i]=0;
cnt=0;
for (int i=1; i<=n; i++)
{
int u;
if (a[i]<=i)
u=i-a[i];
else
u=i+n-a[i];
tmp[u]++;
}
for (int i=0; i<=n; i++)
{
if (tmp[i]>=n-m*2)
ans[++cnt]=i;
}
tot=0;
for (int i=1; i<=cnt; i++)
check(ans[i]);
cout<<tot<<" ";
for (int i=1; i<=tot; i++)
cout<<anss[i]<<" ";
cout<<endl;
}
}
该博客探讨了一种排列变换的算法问题,给定一个由1~n排列经过移动和交换操作后的序列,求可能的初始序列向右移动了多少次。作者提出,由于最多进行n/3次数字交换,至少有n/3数字保持不变,因此可能的移动次数不超过三种。通过计算序列中数字之间的差距,并使用并查集判断是否能在限制交换次数内完成变换,最终找出所有可能的移动次数。代码中展示了如何实现这一思路。
352

被折叠的 条评论
为什么被折叠?



