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

(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】<code><pre>
#include <stdio.h> <br />
#define L 6001
#define wei 208 <br />
void echo(int ans) //make sure printing a 4-wei number
{
if (ans > 999)
{
printf("%d", ans);
}
else if (ans > 99)
{
printf("0%d", ans);
}
else if (ans > 9)
{
printf("00%d", ans);
}
else
{
printf("000%d", ans);
}
} <br />
int main(void)
{
int n, i, j, temp, pro = 0, cn = 0, an[L] = { 0 }, a[L][wei] = { 0 }, c[wei] = { 0 }, ans[wei] = { 0 };
bool zero = false; <br />
scanf("%d\n", &n); <br />
//bases for a, c and an, cn
a[3][0] = 3; a[2][0] = 2; c[0] = 1; <br />
//a[n] = a[n-1] + a[n-2]
for (i = 4; i <= n; i++)
{
//Gao Jin Du Jia Fa
pro = 0;
for (j = 0; j <= an[i - 1]; j++)
{
temp = a[i - 1][j] + a[i - 2][j] + pro;
a[i][j] = temp % 10000;
pro = temp / 10000;
}
if (pro > 0)
{
a[i][j] = pro;
an[i] = j;
}
else an[i] = an[i - 1];
} <br />
// 2^n
for (i = 0; i < n; i++)
{
//Gao Jin Du Jia Fa
pro = 0;
for (j = 0; j <= cn; j++)
{
temp = c[j] * 2 + pro;
c[j] = temp % 10000;
pro = temp / 10000;
}
if (pro > 0)
{
c[j] = pro;
cn++;
}
} <br />
//ans = 2^n - a[n] *2, Gao Jin Du Jia Fa
pro = 0;
for (j = 0; j <= an[n]; j++)
{
temp = a[n][j] * 2 + pro;
a[n][j] = temp % 10000;
pro = temp / 10000;
}
if (pro > 0)
{
an[n]++;
a[n][j] = pro;
} <br />
pro = 0;
for (j = 0; j <= cn; j++)
{
temp = c[j] - a[n][j] + pro;
if (temp < 0)
{
ans[j] = temp + 10000;
pro = -1;
}
else
{
ans[j] = temp;
pro = 0;
}
} <br />
//print the answer, ignoring the zeros in the front
for (i = cn; i >= 0; i--)
{
if (!zero)
{
if (ans[i] != 0)
{
printf("%d", ans[i]);
zero = true;
}
}
else echo(ans[i]);
}
printf("\n"); <br />
return 0;
}</pre></code>
【代码改进空间】
1、将高精度算法函数化;
2、仍然只能通过 Tsinghua Online Judge 40%的数据,其他数据都是Runtime error (exitcode: 11),暂无果。

【优化后AC的代码】
感谢@Plan能抽出时间来AC这道题,同时找到了字符串的高精度加法解决办法,过了100%的数据。以下是参考了她的代码后自己重新几乎是照着写的代码(求2^n的函数从递归形式改成了循环版):<code><pre>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> <br />
char *add(char a[], char b[])
{
int len, i, j, k, up, x, y, z;
char *c, *back; <br />
len = (strlen(a) > strlen(b)) ? strlen(a) + 2 : strlen(b) + 2;
c = (char *)malloc(len*sizeof(char));
back = (char *)malloc(len*sizeof(char)); <br />
i = strlen(a) - 1;
j = strlen(b) - 1;
k = 0; up = 0; <br />
while (i >= 0 || j >= 0)
{
if (i<0) x = '0'; else x = a[i];
if (j<0) y = '0'; else y = b[j];
z = x - '0' + y - '0';
if (up == 1) z += 1;
if (z>9)
{
up = 1; z %= 10;
}
else up = 0;
c[k++] = z + '0';
i--; j--;
}
if (up) c[k++] = '1';
c[k] = '\0'; <br />
//reverse
i = 0;
for (k -= 1; k >= 0; k--) back[i++] = c[k];
back[i] = '\0'; <br />
return back;
} <br />
char *sub(char a[], char b[])
{
int len, i, j, k, down, x, y, z;
char *c, *back; <br />
len = strlen(a);
c = (char *)malloc(len*sizeof(char));
back = (char *)malloc(len*sizeof(char)); <br />
i = strlen(a) - 1;
j = strlen(b) - 1;
k = 0; down = 0; <br />
while (i >= 0 || j >= 0)
{
if (i<0) x = '0'; else x = a[i];
if (j<0) y = '0'; else y = b[j];
z = x - '0' - (y - '0') - down;
if ( z < 0 )
{
down = 1;
z = z + 10;
}
else down = 0;
c[k++] = z + '0';
i--; j--;
}
while (c[--k] == '0') ;
//reverse
i = 0;
for (k; k >= 0; k--)
{
back[i++] = c[k];
} <br />
return back;
} <br />
char *power(int n)
{
int i;
char *temp="2";<br />
for (i = 2; i <= n; i++)
{
temp = add(temp, temp);
} <br />
return temp;
} <br />
char *fib(int n)
{
char *p = "1", *q = "1";
char *s = "1";
int i; <br />
for (i = 0; i < n - 1; i++)
{
s = add(p, q);
p = q;
q = s;
} <br />
return s;
} <br />
int main()
{
int n;
char *mi, *f; <br />
scanf("%d\n", &n); <br />
mi = power(n);
f = fib(n);
f = add(f, f); <br />
printf("%s\n", sub(mi, f)); <br />
return 0;
}
</pre></code>
【参考资料】
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操作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容