E. Permutation Shift

该博客探讨了一种排列变换的算法问题,给定一个由1~n排列经过移动和交换操作后的序列,求可能的初始序列向右移动了多少次。作者提出,由于最多进行n/3次数字交换,至少有n/3数字保持不变,因此可能的移动次数不超过三种。通过计算序列中数字之间的差距,并使用并查集判断是否能在限制交换次数内完成变换,最终找出所有可能的移动次数。代码中展示了如何实现这一思路。

题目地址

题意

给定一个排列,这个排列是由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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值