题目大意ide
对一个初始矩阵进行置换操做
,已知经K次置换后获得的矩阵为
,求一组可能的
。spa
样例解释code
这里只选取第二组样例进行解释。blog
4 2string 3 4 1 2it |
2 3 4 1io |
初始矩阵为,根据Sample Output得知置换操做为
,第一次置换后获得矩阵
,第二次置换后获得矩阵
(和给出的
一致),所以
是一组可能的
。table
解题思路class
考察一个特定的置换操做(咱们暂且称这种矩阵为“置换矩阵”),咱们将其写成另一种形式
,这样写的好处就是咱们能方便的看出有2个循环节,并且咱们会发现无论置换多少次,都是循环节内的数相互动了动位置而已。这启发咱们,也许能够从置换K次的结果
出发,找到其中的循环节,而后反推出置换1次的结果(也就是
)。循环
并且咱们发现,其实K次置换也对应的一个置换矩阵。好比,若是K=2就至关于另外一个置换矩阵
,简写为
。不难发现,随着置换次数的增长,置换矩阵的循环节的个数有可能会发生变化,那么有什么变化的规律么?研究一下就会发现,若是置换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,若是:
合并循环节的关键:假设长度为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; }