1、UVa 10294, First Love Part 2
题意:项链和手镯都是由若干珠子穿成的环形饰物,两个手镯翻转之后若相同则视为相同手镯,而项链不会。换句话说,以下两个图,若是手镯则视为相同,项链则视为不同。当然,无论手镯还是项链,旋转之后相同的一定会被视为相同。
给出珠子个数n和珠子颜色数t,求分别可以穿成多少个手镯,多少个项链。
解法:(此题解直接抄的白书,自己不知道怎么叙述- -)等价类计数问题,很裸的Polya定理(Burnside引理)。
一共两种置换,旋转和翻转。为了叙述方便,将所有珠子标为0, 1, 2...n-1。
旋转:如果逆时针旋转i的间距,则珠子0, i, 2i...构成一个循环。这个循环有n / gcd(n, i)个元素(想想),所以一共有gcd(n, i)个循环。所以,这些循环的不动点总数为a = sum (t^gcd(n, i))(i = 0, 1, 2...n-1)(gcd(n, 0) = n)
翻转:当n为奇数,对称轴只有1种,有n条,所以这些置换的不动点数为b = n * t ^ ((n+1)/2);
当n为偶数,对称轴有两种,每种种n/2条,不动点总数为b = (n / 2) * (t^(n/2+1) + t^(n/2));
项链数a / n,手镯数为(a + b) / (2*n)。(Burnside引理)
tag:math, Polya, Burnside, good
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-09-07 15:33 4 * File Name: (good)-math-UVa-10294.cpp 5 */ 6 #include7 #include 8 9 using namespace std; 10 11 12 typedef long long int64; 13 14 int64 Mypow(int64 a, int64 n) 15 { 16 int64 ret = 1; 17 while (n > 0){ 18 if (n & 1) 19 ret *= a; 20 n >>= 1; 21 a *= a; 22 } 23 return ret; 24 } 25 26 int gcd(int a, int b) 27 { 28 return b == 0 ? a : gcd(b, a%b); 29 } 30 31 int main() 32 { 33 int n, t; 34 while (scanf ("%d%d", &n, &t) != EOF){ 35 int64 ans1 = 0; 36 for (int i = 0; i < n; ++ i) 37 ans1 += Mypow (t, gcd(n, i)); 38 int64 ans2 = ans1; 39 if (n & 1) ans2 += n * Mypow (t, (n+1)/2); 40 else ans2 += n * (Mypow (t, n/2) + Mypow (t, n/2+1)) / 2; 41 printf ("%lld %lld\n", ans1/n, ans2 / (2*n)); 42 } 43 return 0; 44 }
2、NWERC2006, LA 3641, Leonardo's Notebook
POJ 3128 Leonardo's Notebook
题意:给出26个大写字母的一个置换B,问是否存在一个置换A,使得A*A = B。(两道题是一样的)
解法:举例来看一下A^2的运算。A = (a1 a2 a3)(b1 b2 b3 b4),则A^2 = (a1 a2 a3)(a1 a2 a3)(b1 b2 b3 b4)(b1 b2 b3 b4)。而(a1 a2 a3)(a1 a2 a3) = (a1 a3 a2),(b1 b2 b3 b4)(b1 b2 b3 b4) = (b1 b3)(b2 b4)。总结规律得到,两个长度为n的相同循环相乘,当n为奇数时结果也是一个长度为n的循环B,当n为偶数时结果分裂为两个长度为n/2的不相交的循环相乘。
所以,可以将置换B分解成多个不相交的循环,分别考虑每个循环即可。对于某个循环,若它的长度n为奇数,则在A中一定可以构造出一个循环使得其乘积为B中循环;若长度n为偶数,要求A中存在一个2*n的循环即可,即B中长度为n的循环的个数为偶数,否则不存在A使A*A=B。
tag:math, permutation, think, good
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-09-07 18:43 4 * File Name: math-NWERC2006-LA3641.cpp 5 */ 6 #include7 #include 8 #include 9 #include 10 #include 11 #include
3、UVa 11077, Find the Permutations
题意:给出一个{1, 2, 3, ..., n}的一个排列,可以通过一系列的交换变成{1, 2, 3, ..., n}。比如{1, 2, 4, 3}需要1次改变变为{1, 2, 3, 4},{1, 4, 3, 2}需要2次,{1, 2, 3, 4}需要0次。给定n,k,求有多少个排列至少需要k次交换才能变成{1, 2, ..., n}。
解法:首先,用置换的角度来考虑。设任意一个排列P,将他变为{1...n}的变化过程记为置换A,置换A中含有若干个循环(循环之间不会影响交换次数),对某个循环若为(a1, a2, a3...ak),则该循环内交换次数需要交换k-1次,所有循环需要交换次数之和即为P的交换次数。比如,如果A = (a1, a2, a4)a3(a5, a6),则需要2 + 0 + 1次交换。
分析到这里,已经不难看出问题变成了一个递推问题了,但这道题的递推算是比较难推的了- -。用f(i, j)表示至少需要j次交换才能变成{1, 2...i},则f(i, j) = f(i-1, j) + f(i-1, j-1) * (i-1),因为对于元素i,它要么形成自己单独的循环,要么加入任意一个循环的任意一个位置(所有循环共i-1个位置)。边界f(1,1) = f(1, j) = 0,f(i, 0) = 0。
tag:math, permutation, 递推, good
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-09-09 00:02 4 * File Name: math-UVa-11077.cpp 5 */ 6 #include7 #include 8 #include 9 10 using namespace std; 11 12 #define CLR(x) memset(x, 0, sizeof(x)) 13 14 typedef unsigned long long int64; 15 16 const int N = 21; 17 int64 f[N+1][N+1]; 18 19 int64 gao(int64 n, int64 k) 20 { 21 int64& x = f[n][k]; 22 if (x != -1) return x; 23 if (n == 1 && k == 1) return 0; 24 if (k == 0) return 1; 25 if (n == 1) return 0; 26 27 x = gao(n-1, k-1) * (n-1) + gao(n-1, k); 28 return x; 29 } 30 31 int main() 32 { 33 int n, k; 34 for (int i = 0; i <= N; ++ i) 35 for (int j = 0; j <= N; ++ j) 36 f[i][j] = -1; 37 while (scanf ("%d%d", &n, &k) != EOF && !(!n && !k)){ 38 printf ("%llu\n", gao(n, k)); 39 } 40 return 0; 41 }
4、POJ 1286 Necklace of Beads
5、POJ 2409 Let it Bead
它们都为UVa 10294, First Love Part 2(本篇文章第一题)的简略版,只需要求颜色为t的情况下手镯的数量。
Ps:比较坑的是,POJ1286n可能为0,且为0时应该输出0,这两点分别导致我RE一次,WA一次- -
tag:math, Polya, Burnside
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-09-30 21:18 4 * File Name: math-POJ-1286.cpp 5 */ 6 #include7 #include 8 #include 9 #include 10 11 using namespace std; 12 13 #define CLR(x) memset(x, 0, sizeof(x)) 14 15 typedef long long int64; 16 17 int64 pow3[30]; 18 19 int gcd(int a, int b) 20 { 21 return b == 0 ? a : gcd(b, a%b); 22 } 23 24 int64 gao(int64 n) 25 { 26 int64 ret = 0; 27 for (int i = 0; i < n; ++ i) 28 ret += pow3[gcd(i, n)]; 29 30 if (n&1) 31 ret += n * pow3[n/2+1]; 32 else 33 ret += (pow3[n/2] + pow3[n/2+1]) * (n/2); 34 ret /= 2 * n; 35 return ret; 36 } 37 38 int main() 39 { 40 int n; 41 CLR (pow3); 42 pow3[0] = 1; 43 for (int i = 1; i < 24; ++ i) 44 pow3[i] = pow3[i-1] * 3; 45 46 while (scanf ("%d", &n) != EOF && n != -1){ 47 if (!n) printf ("0\n"); 48 else printf ("%lld\n", gao((int64)n)); 49 } 50 return 0; 51 }
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-01 14:05 4 * File Name: math-POJ-2409.cpp 5 */ 6 #include7 #include 8 #include 9 10 using namespace std; 11 12 #define CLR(x) memset(x, 0, sizeof(x)) 13 14 typedef long long int64; 15 16 int64 pow[40]; 17 18 int gcd(int a, int b) 19 { 20 return b == 0 ? a : gcd(b, a%b); 21 } 22 23 int main() 24 { 25 int64 c, n; 26 while (scanf ("%lld%lld", &c, &n) != EOF && n){ 27 CLR (pow); 28 pow[0] = 1; 29 for (int i = 1; i <= n; ++ i) 30 pow[i] = pow[i-1] * c; 31 int64 ans = 0; 32 for (int i = 0; i < n; ++ i) 33 ans += pow[gcd(i, n)]; 34 if (n & 1) 35 ans += n * pow[n/2+1]; 36 else 37 ans += (pow[n/2] + pow[n/2+1]) * (n/2); 38 ans = ans / 2 / n; 39 printf ("%lld\n", ans); 40 } 41 return 0; 42 }
6、POJ 3270 Cow Sorting
题意:现在有n个物品分别标号为0~n-1,他们的重量分别是a[i]。现在,要将他们按照重量大小递增的顺序重新排列。每次只能交换两件物品的位置,并且消耗时间为两件物品重量之和。问最短用多少时间能将重新排序完成。
解法:这道题本质就是一个置换,它里面含有许多循环。
在不考虑消耗时间多少的情况下,只需要每个循环内部互相换位置,就能完成递增的重新排列。此时,耗时最小的情况是,用重量最小的物品与所有物品换位置。对某个循环,记其所含元素个数为cnt,所有元素中重量最小的一个重量mi,所有元素重量和为sum,此种情况所需时间最少为tmp1 = cnt == 1 ? 0 : ((cnt-2)*mi + sum)。
当然,还有另外一种换位置的方法,就是对某个循环,将该循环外的一个物品引入循环。这种方法耗时最小的情况是,用所有物品中重量最小的物品与循环内所有物品交换位置。设所有物品中重量最小的为all_min,则tmp2 = sum + mi + (cnt+1) * all_min。
所以对每个循环,ans += min (tmp1, tmp2)。
其实,只要考虑到最优解可能有两种换位置方法,在手算模拟一下,很容易就能得出以上结论。
tag: math, permutation, think
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-01 18:49 4 * File Name: math-POJ-3270.cpp 5 */ 6 #include7 #include 8 #include 9 #include 10 #include 11 #include 12 13 using namespace std; 14 15 #define CLR(x) memset(x, 0, sizeof(x)) 16 #define PB push_back 17 18 const int maxint = 2147483647; 19 const int N = 10000; 20 21 typedef long long int64; 22 23 bool fla[N+5]; 24 int pat[N+5]; 25 int64 all_min; 26 vector int> > num; 27 28 int64 min(int64 a, int64 b) 29 { 30 return a < b ? a : b; 31 } 32 33 int64 gao(int64 n) 34 { 35 CLR (fla); 36 int64 ret = 0; 37 for (int i = 0; i < n; ++ i) if (!fla[i]){ 38 int64 mi = maxint, sum = 0; 39 int cnt = 0, pos = i; 40 while (!fla[pos]){ 41 ++ cnt; sum += num[pos].first; 42 mi = min(mi, num[pos].first); 43 fla[pos] = 1; 44 pos = pat[pos]; 45 } 46 if (cnt >= 2){ 47 int64 tmp = (int64)(cnt-2) * mi + sum; 48 tmp = min(sum + (int64)(cnt+1) * all_min + mi, tmp); 49 ret += tmp; 50 } 51 } 52 return ret; 53 } 54 55 bool cmp(pair int> a, pair int> b) 56 { 57 return a.first < b.first; 58 } 59 60 int main() 61 { 62 int64 n; 63 while (scanf ("%lld", &n) != EOF){ 64 int64 tmp; 65 num.clear(); 66 for (int i = 0; i < n; ++ i){ 67 scanf ("%lld", &tmp); 68 pair int> p = make_pair(tmp, i); 69 num.PB(p); 70 } 71 72 sort(num.begin(), num.end(), cmp); 73 all_min = num[0].first; 74 CLR (pat); 75 for (int i = 0; i < n; ++ i) 76 pat[i] = num[i].second; 77 78 printf ("%lld\n", gao(n)); 79 } 80 return 0; 81 }
7、POJ 1026 Cipher
题意:给你一个长为n的字符串s[i],和一个置换a[i]。每次操作为:s[a[i]] = s[i]。求进行k次操作以后的字符串。
解法:很简单的置换问题。水题
tag:math, permutation
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-04 11:41 4 * File Name: math-POJ-1026.cpp 5 */ 6 #include7 #include 8 #include 9 #include 10 11 using namespace std; 12 13 #define CLR(x) memset(x, 0, sizeof(x)) 14 #define PB push_back 15 16 const int N = 200; 17 18 typedef vector<int> VI; 19 20 int n; 21 int a[N+5], tmp[N+5]; 22 bool fla[N+5]; 23 24 void gao(int m) 25 { 26 CLR (fla); CLR (tmp); 27 VI cur; 28 for (int i = 0; i < n; ++ i) if (!fla[i]){ 29 cur.clear(); 30 int pos = i; 31 while (!fla[pos]){ 32 fla[pos] = 1; 33 cur.PB (pos); 34 pos = a[pos]; 35 } 36 int sz = cur.size(); 37 for (int i = 0; i < sz; ++ i) 38 tmp[cur[(i+m)%sz]] = cur[i]; 39 } 40 41 char s[N+5], c; 42 int len = 0; 43 CLR (s); 44 scanf ("%c", &c); 45 while (scanf ("%c", &c) != EOF && c != '\n') 46 s[len++] = c; 47 while (len < n) s[len++] = ' '; 48 for (int i = 0; i < n; ++ i) 49 printf ("%c", s[tmp[i]]); 50 printf ("\n"); 51 } 52 53 int main() 54 { 55 while (scanf("%d", &n) != EOF && n){ 56 for (int i = 0; i < n; ++ i){ 57 scanf ("%d", &a[i]); 58 -- a[i]; 59 } 60 61 int x; 62 while (scanf("%d", &x) != EOF && x) 63 gao(x); 64 printf ("\n"); 65 } 66 return 0; 67 }
8、POJ 2154 Color
题意:用n种颜色将长度为n的项链染色,n种颜色不必用完。项链围绕中心旋转后如果不变则视为相同项链。给定n和p,求项链种数%p。
n <= 10^9,1 <= P <= 30000
解法:首先,肯定是最普通的Polya定理,(∑n ^ gcd(n, i)) / n,其中0 <= i < n。但是存在两个问题,一方面,由于取模p和要除的数n并不一定互质,所以不一定存在逆元,不能将除法变为乘法,而考虑到这道题的特殊性,长度和染色种数一样,所以求∑n ^ (gcd(n, i)-1)即可。另一方面,由于n很大,如果枚举所有的i肯定超时。所以考虑用欧拉定理优化。如果d = gcd(n, i),则gcd(n/d, i/d) = 1,所以枚举n的所有约数k,则所求为∑(phi[n/k] * n ^ k)。这样,问题就得到了解答。
值得注意的是,这道题时间卡得很紧,所以每一步都要想到最优化的写法,不然可能总体算法对了,但是仍然会TLE- -。
tag:math, permutation, Euler, good
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-18 10:45 4 * File Name: math-POJ-2154.cpp 5 */ 6 #include7 #include 8 #include 9 #include 10 #include 11 12 using namespace std; 13 14 #define CLR(x) memset(x, 0, sizeof(x)) 15 const int N = 1000000; 16 17 int phi[N+5]; 18 int vis[50005], prm[50005]; 19 int mod, nprm; 20 21 void sieve(int n) 22 { 23 int m = (int)sqrt(n+0.5); 24 CLR (vis); 25 for (int i = 2; i <= m; ++ i) if (!vis[i]) 26 for (int j = i*i; j <= n; j += i) vis[j] = 1; 27 } 28 29 int primes(int n) 30 { 31 sieve(n); 32 int ret = 0; 33 for (int i = 2; i <= n; ++ i) 34 if (!vis[i]) prm[ret++] = i; 35 return ret; 36 } 37 38 void phi_table(int n) 39 { 40 CLR (phi); 41 phi[1] = 1; 42 for (int i = 2; i <= n; ++ i) if (!phi[i]){ 43 for (int j = i; j <= n; j += i){ 44 if (!phi[j]) phi[j] = j; 45 phi[j] -= phi[j]/i; 46 } 47 } 48 } 49 50 int euler_phi(int n) 51 { 52 if (n <= N) return phi[n] % mod; 53 54 int m = (int)sqrt(n + 0.5); 55 int ans = n; 56 for (int i = 0; i i) 57 if (!(n % prm[i])){ 58 ans -= ans/prm[i]; 59 while (!(n % prm[i])) n /= prm[i]; 60 } 61 if (n > 1) ans -= ans/n; 62 return ans % mod; 63 } 64 65 int pow_mod(int p, int n) 66 { 67 int ret = 1; 68 p %= mod; 69 while (n > 0){ 70 if (n & 1) ret = (ret * p) % mod; 71 n >>= 1; 72 p = (p * p) % mod; 73 } 74 return ret; 75 } 76 77 int Polya(int n) 78 { 79 int ret = 0; 80 for (int i = 1; i*i <= n; ++ i) if (n % i == 0){ 81 ret = (ret + pow_mod(n, n/i-1) * euler_phi(i)) % mod; 82 if (i*i < n) 83 ret = (ret + (pow_mod(n, i-1) % mod) * (euler_phi(n/i) % mod)) % mod; 84 } 85 if (ret < 0) ret += mod; 86 return ret; 87 } 88 89 int main() 90 { 91 phi_table(N); 92 nprm = primes(50000); 93 int T, n; 94 scanf ("%d", &T); 95 while (T--){ 96 scanf ("%d%d", &n, &mod); 97 printf ("%d\n", Polya(n)); 98 } 99 return 0; 100 }
9、POJ 2888 Magic Bracelet
题意:用m种颜色的珠子串长度为n的项链,有颜色相邻限制,即对每种颜色,它不能和某些颜色的珠子相邻。若旋转相同则视为同一种项链。求这样的项链数量mod 9973。
1 ≤ n ≤ 109,1 ≤ m ≤ 10
解法:首先,若没有颜色相邻限制,则就可以用burnside引理轻易解决。对于颜色限制,就相当于解决这个问题,设f(x)是长度为x且该链的第一颗珠子和最后一颗珠子可以相领的链有多少种。求f(x)可以考虑递推,但是由于n可能为10^9,所以无论时间还是空间复杂度都无法接受。所以可以用矩阵来解决这个问题。设I为表示颜色是否可以相邻的矩阵(可相邻记为1),则f(x)为 I^(x+1)的主对角线元素之和。
tag:math, Polya, 矩阵乘法快速幂, 乘法逆, good
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-17 18:26 4 * File Name: math-POJ-2888.cpp 5 */ 6 #include7 #include 8 #include 9 #include 10 11 using namespace std; 12 13 const int mod = 9973; 14 typedef int matrix[15][15]; 15 16 matrix pat; 17 int n, m, k; 18 19 void init() 20 { 21 scanf ("%d%d%d", &n, &m, &k); 22 23 for (int i = 0; i < m; ++ i) 24 for (int j = 0; j < m; ++ j) 25 pat[i][j] = 1; 26 while (k--){ 27 int ta, tb; 28 scanf ("%d%d", &ta, &tb); 29 -- ta; -- tb; 30 pat[ta][tb] = 0; 31 pat[tb][ta] = 0; 32 } 33 } 34 35 int euler_phi(int n) 36 { 37 int m = (int)sqrt(n + 0.5); 38 int ans = n; 39 for (int i = 2; i <= m; ++ i) 40 if (!(n % i)){ 41 ans = ans / i * (i-1); 42 while (!(n % i)) n /= i; 43 } 44 if (n > 1) ans = ans / n * (n-1); 45 return (ans % mod); 46 } 47 48 void mtx_mul(matrix& A, matrix B) 49 { 50 matrix ret; 51 for (int i = 0; i < m; ++ i) 52 for (int j = 0; j < m; ++ j){ 53 ret[i][j] = 0; 54 for (int k = 0; k < m; ++ k) 55 ret[i][j] = (ret[i][j] + A[i][k] * B[k][j]) % mod; 56 if (ret[i][j] < 0) ret[i][j] += mod; 57 } 58 59 for (int i = 0; i < m; ++ i) 60 for (int j = 0; j < m; ++ j) 61 A[i][j] = ret[i][j]; 62 } 63 64 void mtx_pow(matrix& A, int n) 65 { 66 -- n; 67 if (!n) return; 68 69 matrix ret; 70 for (int i = 0; i < m; ++ i) 71 for (int j = 0; j < m; ++ j) 72 ret[i][j] = A[i][j]; 73 while (n > 0){ 74 if (n & 1) mtx_mul(ret, A); 75 n >>= 1; 76 mtx_mul(A, A); 77 } 78 79 for (int i = 0; i < m; ++ i) 80 for (int j = 0; j < m; ++ j) 81 A[i][j] = ret[i][j]; 82 } 83 84 int pow_mod(int n, int p) 85 { 86 n %= mod; 87 int ret = 1; 88 while (p > 0){ 89 if (p & 1) ret = (ret * n) % mod; 90 p >>= 1; 91 n = (n * n) % mod; 92 } 93 return ret; 94 } 95 96 int solve(int n) 97 { 98 matrix A; 99 for (int i = 0; i < m; ++ i) 100 for (int j = 0; j < m; ++ j) 101 A[i][j] = pat[i][j]; 102 mtx_pow(A, n); 103 int ret = 0; 104 for (int i = 0; i < m; ++ i) 105 ret = (ret + A[i][i]) % mod; 106 if (ret < 0) ret += mod; 107 return ret; 108 } 109 110 int Polya() 111 { 112 int ret = 0; 113 for (int i = 1; i*i <= n; ++ i) if (n % i == 0){ 114 if (i*i < n) ret = (ret + solve(i)*euler_phi(n/i) + solve(n/i)*euler_phi(i)) % mod; 115 else ret = (ret + solve(i) * euler_phi(i)) % mod; 116 } 117 return (ret * pow_mod(n%mod, mod-2)) % mod; 118 } 119 120 int main() 121 { 122 int T; 123 scanf ("%d", &T); 124 while (T--){ 125 init(); 126 printf ("%d\n", Polya()); 127 } 128 return 0; 129 }
10、POJ 1721 CARDS
题意:定义一种操作double shuffle,对置换a[],将所有a[i]处的牌变为a[a[i]]。给出一个置换T'进行了m次操作以后的结果,求原置换T。
原置换循环节数为1,长度n <= 1000,且n为奇数,s <= 1000。
解法:在写解法之前,先写一个问题。就是题目并没有告诉原置换循环节数为1,但是通过题面的这句话可以得知“Alice first writes down all the numbers from 1 to N in some random order: a1, a2, ..., aN. Then she arranges the cards so that the position ai holds the card numbered ai+1, for every 1 <= i <= N-1, while the position aN holds the card numbered a1. ”。因为x[a[0]] = a[1],x[a[1]] = a[2],x[a[2]] = a[3]....
两种解法:
法一。这种解法偏向于暴力。首先,对任意循环,总存在一个数k使得T^k = T,首先暴力处理T可以求得k,然后,将T'进行double shuffle操作k - (m%k)次即可。模拟可以通过,32ms。
法二。置换群的开方运算。
首先,要注意到,double shuffle操作即是将置换T变为T^2。其次,注意到置换为单循环且长度n为奇数。所以,置换T在变成T'的过程中并没有发生循环分裂,所以直接用以下公式解决该问题:设数组a[] = T,a'[] = T',(a[0] = 0),则a'[i] = a[(i*(2^m)) % n]。(该公式具体参见潘震皓的《置换群快速幂运算 研究与探讨》,该文讲得非常好)
tag:math, permutation, 置换群的开方, good
解法1:
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-20 15:09 4 * File Name: math-POJ-1721.cpp 5 */ 6 #include7 #include 8 #include 9 #include 10 11 using namespace std; 12 13 #define CLR(x) memset(x, 0, sizeof(x)) 14 #define PB push_back 15 const int N = 1000; 16 typedef vector<int> VI; 17 18 int a[N+5], b[2][N+5]; 19 20 int gao(int n, int m) 21 { 22 int k = 0, idx = 0; 23 for (int i = 0; i < n; ++ i) 24 b[0][i] = a[i]; 25 while (1){ 26 bool fla = false; 27 for (int i = 0; i < n; ++ i) 28 b[idx^1][i] = b[idx][b[idx][i]]; 29 idx ^= 1; 30 ++ k; 31 for (int i = 0; i < n; ++ i) 32 if (b[idx][i] != a[i]) fla = 1; 33 if (!fla) break; 34 } 35 36 m %= k; 37 int tim = k - m; 38 idx = 0; 39 for (int i = 0; i < n; ++ i) 40 b[0][i] = a[i]; 41 while (tim--){ 42 for (int i = 0; i < n; ++ i) 43 b[idx^1][i] = b[idx][b[idx][i]]; 44 idx ^= 1; 45 } 46 return idx; 47 } 48 49 int main() 50 { 51 int n, m; 52 while (scanf ("%d%d", &n, &m) != EOF){ 53 for (int i = 0; i < n; ++ i){ 54 scanf ("%d", &a[i]); 55 -- a[i]; 56 } 57 int idx = gao(n, m); 58 for (int i = 0; i < n; ++ i) 59 printf ("%d\n", b[idx][i] + 1); 60 } 61 return 0; 62 }
解法2:
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-21 15:26 4 * File Name: math-POJ-1721-2.cpp 5 */ 6 #include7 #include 8 #include 9 10 using namespace std; 11 12 #define CLR(x) memset(x, 0, sizeof(x)) 13 const int N = 1000; 14 15 int a[N+5], cir[N+5], ans[N+5], tmp[N+5]; 16 17 int pow_mod(int p, int n, int mod) 18 { 19 p %= mod; 20 int ret = 1; 21 while (n > 0){ 22 if (n & 1) ret = (ret * p) % mod; 23 n >>= 1; 24 p = (p * p) % mod; 25 } 26 return ret; 27 } 28 29 void gao(int n, int m) 30 { 31 int pos = 0; 32 while (a[pos]) 33 pos = a[pos]; 34 cir[0] = pos = 0; 35 int idx = 1; 36 while (a[pos]){ 37 cir[idx++] = a[pos]; 38 pos = a[pos]; 39 } 40 int k = pow_mod(2, m, n); 41 for (int i = 0; i < n; ++ i) 42 tmp[(i*k)%n] = cir[i]; 43 for (int i = 0; i < n-1; ++ i) 44 ans[tmp[i]] = tmp[i+1]; 45 ans[tmp[n-1]] = tmp[0]; 46 } 47 48 int main() 49 { 50 int n, m; 51 while (scanf ("%d%d", &n, &m) != EOF){ 52 for (int i = 0; i < n; ++ i){ 53 scanf ("%d", &a[i]); 54 -- a[i]; 55 } 56 gao(n, m); 57 for (int i = 0; i < n; ++ i) 58 printf ("%d\n", ans[i] + 1); 59 } 60 return 0; 61 }
11、POJ 3590 The shuffle Problem
题意:给定一个n,求长度为n的置换,使得它的置换周期最大。输出置换周期,并且输出置换本身。若有置换周期相同的置换,输出字典序小的。
n <= 100
解法:首先,对于一个置换,它的置换周期就是所有循环长度的最小公倍数。那么,这道题就变为求n[i],使得n[i]之和为n,且n[i]的最小公倍数最大。
用贪心思想:若要在n[i]之和为n的情况下使得他们的最小公倍数最大,则n[i]均为p^t的形式,其中p为质数,t为任意正整数。
这样,生下来的问题就可以用dp解决了。
要注意的是,不能在用贪心思想之前直接这样dp(我是这样WA的):设dp[i]表示和为i的若干数的最大最小公倍数,dp[i] = max(dp[i], lcm(dp[j], dp[i-j]),j = 1 to i-1。因为虽然”在m > k的情况下,有dp[m] > dp[k]“,但是”在m > k的情况下,lcm(dp[t], dp[m]) > lcm(dp[t], dp[k])“是错误的,所以这种dp方法也是错误的。
tag:math, permutation, dp, greedy
Ps:官方题解:http://hi.baidu.com/zzningxp/item/d2c8722ff39f3e4747996256
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-21 23:44 4 * File Name: math-POJ-3590.cpp 5 */ 6 #include7 #include 8 #include 9 #include 10 #include 11 12 using namespace std; 13 14 #define CLR(x) memset(x, 0, sizeof(x)) 15 #define PB push_back 16 const int N = 100; 17 typedef vector<int> VI; 18 typedef long long int64; 19 20 int64 ans = 0; 21 int prm[30] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101}; 22 VI cur, tmp; 23 24 void dp(int n, int sum, int l) 25 { 26 if (l > ans){ 27 ans = l; 28 cur = tmp; 29 for (int i = 0; i < sum; ++ i) 30 cur.PB (1); 31 } 32 33 for (int i = n+1; prm[i] <= sum; ++ i){ 34 int xxx = prm[i]; 35 while (xxx <= sum){ 36 tmp.PB (xxx); 37 dp(i, sum-xxx, xxx*l); 38 tmp.pop_back(); 39 xxx *= prm[i]; 40 } 41 } 42 } 43 44 int main() 45 { 46 int T; 47 scanf ("%d", &T); 48 while (T--){ 49 int n; 50 scanf ("%d", &n); 51 52 if (n == 1){ 53 printf ("1 1\n"); 54 continue; 55 } 56 57 cur.clear(); ans = 0; 58 for (int i = 0; prm[i] <= n; ++ i){ 59 int xxx = prm[i]; 60 while (xxx <= n){ 61 tmp.clear(); 62 tmp.PB (xxx); 63 dp(i, n-xxx, xxx); 64 xxx *= prm[i]; 65 } 66 } 67 68 int64 mul = 1; 69 for (int i = 0; i < cur.size(); ++ i) 70 mul *= cur[i]; 71 printf ("%lld", mul); 72 sort(cur.begin(), cur.end()); 73 int idx = 0; 74 for (int i = 0; i < cur.size(); ++ i){ 75 for (int j = 1; j < cur[i]; ++ j) 76 printf (" %d", idx+1+j); 77 printf (" %d", idx+1); 78 idx += cur[i]; 79 } 80 printf ("\n"); 81 } 82 return 0; 83 }