zoukankan      html  css  js  c++  java
  • CF1553 E. Permutation Shift

    Problem - 1553E - Codeforces

    题意:

    有一个初始排列1 2 3……n,再给出一个目标排列和一个m(m<=n/3),要求把初始排列变为目标排列

    首先,你可以将初始排列的最后k个数字挪到最前面,然后可以选2个数字进行交换,交换至多进行m次

    问有多少个k满足能够使初始排列变为目标排列

    首先解决一个经典问题

    对于一个排列A和一个排列B,每次可以选2个数字交换,问最少交换多少次可以使A变成B

    我们在所有的A[i]和B[i]之间连边,这样会得到若干个环

    自环不需要交换

    一个大小为L的环需要L-1次交换

    所以最少交换次数等于 n-环的个数

    所以如果确定了k,那么可以用O(n)的时间检验出能否至多交换m次变为目标排列

    但是k不能枚举,这样复杂度就是O(n^2)了

    这时就要看m<=n/3的限制了

    一次交换至多使2个数到目标位置,至多交换m次,所以至多有2m个数可以不在目标位置

    即至少要有n-2m个数在目标位置

    因为m<=n/3,所以n-2m>=n/3

    即一个排列只有至少有n/3个数在正确位置,才有可能在m次交换之内变为目标排列

    而1个数只有一个唯一的k能够使它在目标位置

    而一个可能的k又需要有至少n/3个数在它的目标位置

    所以至多只有3个k有可能满足条件

    所以我们只需要检验3个k就可以

    怎么确定这3个k是谁

    把每个数都减1,这样排列变为[0—n-1],因为循环移位取模用0比较方便

    位置下标从0开始,初始排列第i个数在位置i,当选定k之后,位置i的数会变为(i-k+n)%n

    设这个位置的目标数位x,那么就是(i-k+n)%n=x

    所以k=(i-x+n)%n

    对于每个位置都算出一个k,取最多的3个k即可

    #include<bits/stdc++.h>
    
    #define N 300003
    
    int n,m,a[N],sk[N];
    int fa[N];
    bool vis[N];
    
    int find(int i) { return fa[i]==i ? i : fa[i]=find(fa[i]); }
    
    void unionn(int i,int j)
    {
        fa[i]=find(i);
        fa[j]=find(j);
        if(fa[i]!=fa[j]) fa[fa[i]]=j;
    }
    
    bool check(int k)
    {
        for(int i=0;i<n;++i) fa[i]=i;
        for(int i=0;i<n;++i) unionn(a[i],(i-k+n)%n);
        for(int i=0;i<n;++i) 
        {
            vis[i]=false;
            fa[i]=find(i);
        }
        int h=0;
        for(int i=0;i<n;++i)
            if(!vis[fa[i]])
            {
                vis[fa[i]]=true;
                ++h;
            }    
        return n-h<=m;
    }
    
    int main()
    {
        int T;
        int sum,ans[4];
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d",&n,&m);
            for(int i=0;i<n;++i) 
            {
                scanf("%d",&a[i]);
                a[i]--;
            }
            for(int i=0;i<n;++i) sk[i]=0;
            for(int i=0;i<n;++i) sk[(i-a[i]+n)%n]++;
            sum=0;
            for(int i=0;i<n;++i)
                if(sk[i]>=n/3)
                    if(check(i)) ans[++sum]=i;
            printf("%d ",sum);
            for(int i=1;i<=sum;++i) printf("%d ",ans[i]);
            printf("
    "); 
        }
    }
    作者:xxy
    本文版权归作者和博客园共有,转载请用链接,请勿原文转载,Thanks♪(・ω・)ノ。
  • 相关阅读:
    页面打开 抛出w3wp.exe 中发生未处理异常
    link
    带下拉子菜单的导航菜单
    横向列表菜单
    Codeforces Round #640 (Div. 4)
    【剑指Offer】06. 从尾到头打印链表
    【剑指Offer】65. 不用加减乘除做加法
    【剑指Offer】15. 二进制中1的个数
    【剑指Offer】03. 数组中重复的数字(哈希)
    【LeetCode】50. Pow(x, n)(快速幂)
  • 原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/15364224.html
Copyright © 2011-2022 走看看