【Tsinghua OJ】多米诺骨牌(domino)问题

Posted on 2014-10-21 12:34  Maples7  阅读(1763)  评论(0)    收藏  举报

(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操作。