NCPC 2013: Dance Reconstruction

题目大意

    对一个初始矩阵NCPC 2013: Dance Reconstruction进行置换操作NCPC 2013: Dance Reconstruction,已知经K次置换后得到的矩阵为NCPC 2013: Dance Reconstruction,求一组可能的NCPC 2013: Dance Reconstruction

 

样例解释

    这里只选取第二组样例进行解释。

4 2

3 4 1 2

2 3 4 1

    初始矩阵为NCPC 2013: Dance Reconstruction,根据Sample Output得知置换操作为NCPC 2013: Dance Reconstruction,第一次置换后得到矩阵NCPC 2013: Dance Reconstruction,第二次置换后得到矩阵NCPC 2013: Dance Reconstruction(和给出的NCPC 2013: Dance Reconstruction一致),因此NCPC 2013: Dance Reconstruction是一组可能的NCPC 2013: Dance Reconstruction

 

解题思路

    考察一个特定的置换操作NCPC 2013: Dance Reconstruction(我们暂且称这种矩阵为“置换矩阵”),我们将其写成另外一种形式NCPC 2013: Dance Reconstruction,这样写的好处就是我们能方便的看出有2个循环节,而且我们会发现不管置换多少次,都是循环节内的数相互动了动位置而已。这启发我们,也许可以从置换K次的结果NCPC 2013: Dance Reconstruction出发,找到其中的循环节,然后反推出置换1次的结果(也就是NCPC 2013: Dance Reconstruction)。

    而且我们发现,其实K次置换也对应的一个置换矩阵。比如NCPC 2013: Dance Reconstruction,如果K=2就相当于另一个置换矩阵NCPC 2013: Dance Reconstruction,简写为NCPC 2013: Dance Reconstruction。不难发现,随着置换次数的增加,置换矩阵的循环节的个数有可能会发生变化,那么有什么变化的规律么?研究一下就会发现,如果置换1次对应的置换矩阵中有一个长度为m的循环节,当置换次数K满足gcd(m,K) = 1时,这个循环节在置换K次对应的置换矩阵中仍是一个长度为m的循环节,否则这个循环节将会变成gcd(m,K)个长度为m/gcd(m,K)的循环节。反过来讲,假设置换K次之后有一个循环节的长度为n,如果gcd(n,K) > 1,那么这个一定是某个长度为m的较长的循环节“分裂”出来,并且必须满足m/gcd(m,K) = n,同时包含他在内至少应有gcd(m,K)个长度为n的循环节(这里只是给出了一个对长度为n的循环节的个数给出了一个较为宽泛的约束,严格的约束会在后面提到);如果gcd(n,K) = 1,那么既有可能是“分裂”出来的,也有可能不是“分裂”出来的,对于此题而言,我们可以认为其不是“分裂”出来的,可以简化问题的求解。

    经过上述分析之后,形成了一个大致的解题思路——分情况处理置换K次对应的置换矩阵中的各个循环节。设循环节的长度为n,如果:

  1. gcd(n,K) = 1。那么直接反推出置换1次时对应的置换矩阵即可。
  2. gcd(n,K) > 1。那么这个一定是某个长度为m的较大的循环节“分裂”出来,而且满足m/gcd(m,K) = n。这时,我们需要把gcd(m, K)个长为n小循环节合并成一个长为m的大循环节并反推出置换1次时对应的置换矩阵。

    合并循环节的关键:假设长度为n的循环节一共有cnt个,我们要找到一个最小的gcd(m,K)使得m=gcd(m,K)*n成立。如果这个最小的gcd(m, K)是cnt的约数,那么一定有解,将gcd(m, K)个长度为n的小循环节合成一个长度m的大循环即可;否则无解。

    如何找到这个最小的gcd(m,K)?不能单纯地认为gcd(m,K) = gcd(n,K),比如n = 2, K = 4,那么gcd(m,K) = gcd(n,K) = 2, m = gcd(m,K)*n = 4,这样就又能推得gcd(m,K) = 4,矛盾。上面那个例子之所以出现矛盾就是因为n承担了一部分公因子,因此我们要让n不承担任何公因子:对于任意一个n里面的素因子p,如果K里面也有p,那么p在K中是多少次方就应当在gcd(m,K)中是多少次方。不过实际写代码的时候没必要按素因子逐个去检查,只要不断地求K和n的公因数d,并将K除以d,直到d = 1为止,将前面所有的d累乘就是gcd(m,K)。

    至于怎么根据置换K次时对应的置换矩阵反推出置换1次时对应的置换矩阵,在这里就不细说了,这个不难。

    P.S. 实际写代码的时候就会发现其实并不用分情况讨论gcd(n,K) = 1还是gcd(n,K) > 1,因为当gcd(n,K) = 1时求得的最小的gcd(m,K)就是1,而且1是任何数的约数,自然就会认为这个循环节是由1个循环节“分裂”得到的(也就是没“分裂”)。

#include<cstdio>

#include<cstring>

#include<vector>

#include<algorithm>

#define MAXN 10010

typedef std::vector<int> VI;

int N, K, f[MAXN];

std::vector<VI> c; // cycles

bool cmp(VI v1, VI v2) {

    return v1.size() < v2.size();

}

int gcd(int x, int y) {

    return y == 0 ? x : gcd(y, x % y);

}

bool vis[MAXN];

// find the cycles

void divideCycle() {

    c.clear();

    memset(vis, 0, sizeof(vis[0]) * (N + 1));

    for(int i = 1; i <= N; i ++) {

        if(!vis[i]) {

            VI v;

            for(int j = i; !vis[j]; j = f[j]) {

                vis[j] = true;

                v.push_back(j);

            }

            c.push_back(v);

        }

    }

    std::sort(c.begin(), c.end(), cmp);

}

int ans[MAXN], a[MAXN];

void process() {

    int cn = c.size();

    for(int i = 0, j = 0; i < cn; i ++) {

        if(i == cn - 1 || c[i].size() != c[i + 1].size()) {

            int n = c[i].size(); // n是小循环节的长度

            int t = K, d, g = 1; // g是分裂出来的循环节个数

            while((d = gcd(n, t)) != 1) {

                g *= d, t /= d;

            }

            int m = g * n; // m是大循环节的长度



            if((i - j + 1) % g == 0) {

                for(; j <= i; j += g) { // j~j+g-1 合并成一个大循环节

                    for(int s = 0; s < g; s ++) {

                        for(int id = 0, loc = s; id < n; id ++, loc = (loc + K) % m) {

                            a[loc] = c[j + s][id];

                        }

                    }

                    a[m] = a[0];

                    for(int id = 0; id < m; id ++) {

                        ans[a[id]] = a[id + 1];

                    }

                }

            } else {

                printf("Impossible\n");

                return ;

            }

        }

    }



    printf("%d", ans[1]);

    for(int i = 2; i <= N; i ++) {

        printf(" %d", ans[i]);

    }

    printf("\n");

}

int main() {

    while(scanf("%d%d", &N, &K) == 2) {

        for(int i = 1; i <= N; i ++) {

            scanf("%d", &f[i]);

        }

        divideCycle();

        process();

    }

    return 0;

}

 

你可能感兴趣的:(struct)