(domino.c/cpp)
【问题描述】
小牛牛对多米诺骨牌有很大兴趣,然而她的骨牌比较特别,只有黑色和白色的两种。她觉 得如果存在连续三个骨牌是同一种颜色,那么这个骨牌排列便是不美观的。现在她有n个骨牌要来排列,她想知道不美观的排列的个数。由于数字较大,数学不好的 她不会统计,所以请你来帮忙。希望你帮她求出不美观的排列的个数。
【输入数据】
只有一个正整数,即要排列的骨牌个数。
【输出数据】
一个数,即不美观的排列个数。
【样例输入】
4
【样例输出】
6
【样例解释】
有四种不美观的排列。
黑黑黑黑,白白白白,黑黑黑白,白白白黑,黑白白白,白黑黑黑
【数据范围】
20%的数据,n<=60;
50%的数据,n<=6000;
100%的数据,n<=10000。
时间限制: 1 sec
空间限制: 256 MB
【提示】
动态规划、高精度加法。
—————————————————————————————————
【solution】
虽然只是Tutorial里面的题,虽然听说现在是小学僧的练习题(T_T),不过还真是想了辣么一会儿。算算真是已经有4年多没碰过这些东西了,为了完成这门课作业也真是找回了当初的感觉,真是怀念这种一道一道题“过关斩将”的感觉,已经很久不曾有这种感觉了。
回到正题。这道题初看很容易去正向考虑如何统计“不美观”的排列个数,甚至会误入使用组合数学的错误算法。根据提示,往动态规划方面想,会发现,实 际上,这道题需要反向来思考,即考虑“美观”的排列个数。那么,题目转化为求解连续颜色不超过3(不包括3)的排列个数 a,然后再用所有的排列个数(2^n)减去 a 即得问题解。再细想,这不跟动态规划的经典问题——上楼梯问题 很像吗?
于是,问题得解:
对于每一个色块(连续的 1 个或者 2 个相同颜色的白色或者黑色色块),就相当于上楼梯问题中的上升一阶或者两阶,所以这里其实我们完全可以忽略到颜色这个因素(最后再把得到的上阶梯的总数乘 以2,因为把所有的色块全部反转一次颜色都可以得到原来那种的状态的 twin solution,而上楼梯问题并未考虑颜色问题,只是简单的划分为一次动作,这个问题正是因为颜色来划分的),而是把一个色块等同为上楼梯问题中的一次 动作。
状态方程为:f[n] = f[n-1] + f[n-2]。初始条件 f[1] = 1; f[2] = 2。
也就是不严格对应项数的著名的斐波拉契数列。
最后的结果为 2^n - 2*f[n]。
由于问题数据规模较大,最后还要用高精度加法来实现。
【source code】
1 #include <stdio.h> 2 3 #define L 6001 4 #define wei 208 5 6 void echo(int ans) //make sure printing a 4-wei number,此函数可以用格式控制方式: printf("%04d", ans); 简单代替。C++中类似使用setfill('0') setw(30)等 7 { 8 if (ans > 999) 9 { 10 printf("%d", ans); 11 } 12 else if (ans > 99) 13 { 14 printf("0%d", ans); 15 } 16 else if (ans > 9) 17 { 18 printf("00%d", ans); 19 } 20 else 21 { 22 printf("000%d", ans); 23 } 24 } 25 26 int main(void) 27 { 28 int n, i, j, temp, pro = 0, cn = 0, an[L] = { 0 }, a[L][wei] = { 0 }, c[wei] = { 0 }, ans[wei] = { 0 }; 29 bool zero = false; 30 31 scanf("%d\n", &n); 32 33 //bases for a, c and an, cn 34 a[3][0] = 3; a[2][0] = 2; c[0] = 1; 35 36 //a[n] = a[n-1] + a[n-2] 37 for (i = 4; i <= n; i++) 38 { 39 //Gao Jin Du Jia Fa 40 pro = 0; 41 for (j = 0; j <= an[i - 1]; j++) 42 { 43 temp = a[i - 1][j] + a[i - 2][j] + pro; 44 a[i][j] = temp % 10000; 45 pro = temp / 10000; 46 } 47 if (pro > 0) 48 { 49 a[i][j] = pro; 50 an[i] = j; 51 } 52 else an[i] = an[i - 1]; 53 } 54 55 // 2^n 56 for (i = 0; i < n; i++) 57 { 58 //Gao Jin Du Jia Fa 59 pro = 0; 60 for (j = 0; j <= cn; j++) 61 { 62 temp = c[j] * 2 + pro; 63 c[j] = temp % 10000; 64 pro = temp / 10000; 65 } 66 if (pro > 0) 67 { 68 c[j] = pro; 69 cn++; 70 } 71 } 72 73 //ans = 2^n - a[n] *2, Gao Jin Du Jia Fa 74 pro = 0; 75 for (j = 0; j <= an[n]; j++) 76 { 77 temp = a[n][j] * 2 + pro; 78 a[n][j] = temp % 10000; 79 pro = temp / 10000; 80 } 81 if (pro > 0) 82 { 83 an[n]++; 84 a[n][j] = pro; 85 } 86 87 pro = 0; 88 for (j = 0; j <= cn; j++) 89 { 90 temp = c[j] - a[n][j] + pro; 91 if (temp < 0) 92 { 93 ans[j] = temp + 10000; 94 pro = -1; 95 } 96 else 97 { 98 ans[j] = temp; 99 pro = 0; 100 } 101 } 102 103 //print the answer, ignoring the zeros in the front 104 for (i = cn; i >= 0; i--) 105 { 106 if (!zero) 107 { 108 if (ans[i] != 0) 109 { 110 printf("%d", ans[i]); 111 zero = true; 112 } 113 } 114 else echo(ans[i]); 115 } 116 printf("\n"); 117 118 return 0; 119 }
【代码改进空间】
1、将高精度算法函数化;
2、仍然只能通过 Tsinghua Online Judge 40%的数据,其他数据都是Runtime error (exitcode: 11),暂无果。
【优化后AC的代码】
感谢@Plan能抽出时间来AC这道题,同时找到了字符串的高精度加法解决办法,过了100%的数据。以下是参考了她的代码后自己重新几乎是照着写的代码(求2^n的函数从递归形式改成了循环版):
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 5 char *add(char a[], char b[]) 6 { 7 int len, i, j, k, up, x, y, z; 8 char *c, *back; 9 10 len = (strlen(a) > strlen(b)) ? strlen(a) + 2 : strlen(b) + 2; 11 c = (char *)malloc(len*sizeof(char)); 12 back = (char *)malloc(len*sizeof(char)); 13 14 i = strlen(a) - 1; 15 j = strlen(b) - 1; 16 k = 0; up = 0; 17 18 while (i >= 0 || j >= 0) 19 { 20 if (i<0) x = '0'; else x = a[i]; 21 if (j<0) y = '0'; else y = b[j]; 22 z = x - '0' + y - '0'; 23 if (up == 1) z += 1; 24 if (z>9) 25 { 26 up = 1; z %= 10; 27 } 28 else up = 0; 29 c[k++] = z + '0'; 30 i--; j--; 31 } 32 if (up) c[k++] = '1'; 33 c[k] = '\0'; 34 35 //reverse 36 i = 0; 37 for (k -= 1; k >= 0; k--) back[i++] = c[k]; 38 back[i] = '\0'; 39 40 return back; 41 } 42 43 char *sub(char a[], char b[]) 44 { 45 int len, i, j, k, down, x, y, z; 46 char *c, *back; 47 48 len = strlen(a); 49 c = (char *)malloc(len*sizeof(char)); 50 back = (char *)malloc(len*sizeof(char)); 51 52 i = strlen(a) - 1; 53 j = strlen(b) - 1; 54 k = 0; down = 0; 55 56 while (i >= 0 || j >= 0) 57 { 58 if (i<0) x = '0'; else x = a[i]; 59 if (j<0) y = '0'; else y = b[j]; 60 z = x - '0' - (y - '0') - down; 61 if ( z < 0 ) 62 { 63 down = 1; 64 z = z + 10; 65 } 66 else down = 0; 67 c[k++] = z + '0'; 68 i--; j--; 69 } 70 while (c[--k] == '0') ; 71 72 //reverse 73 i = 0; 74 for (k; k >= 0; k--) 75 { 76 back[i++] = c[k]; 77 } 78 79 return back; 80 } 81 82 char *power(int n) 83 { 84 int i; 85 char *temp="2"; 86 87 for (i = 2; i <= n; i++) 88 { 89 temp = add(temp, temp); 90 } 91 92 return temp; 93 } 94 95 char *fib(int n) 96 { 97 char *p = "1", *q = "1"; 98 char *s = "1"; 99 int i; 100 101 for (i = 0; i < n - 1; i++) 102 { 103 s = add(p, q); 104 p = q; 105 q = s; 106 } 107 108 return s; 109 } 110 111 int main() 112 { 113 int n; 114 char *mi, *f; 115 116 scanf("%d\n", &n); 117 118 mi = power(n); 119 f = fib(n); 120 f = add(f, f); 121 122 printf("%s\n", sub(mi, f)); 123 124 return 0; 125 }
【参考资料】
1:http ://www.cnblogs.com/kuangbin/archive/2011/07/22/2113836.html 高精度加法的C++实现;
2:http://blog.sina.com.cn/s/blog_993d2542010143qw.html Fibonacci数列的第N项 log(N)算法(未用到)。
有几点:
1)由于数据规模,四位进一次位的int版高精也无法AC掉所有数据,只能用string来解决了。
2)要注意高精度运算string的顺序是不是跟数字顺序一致,所以代码中有reverse操作。