★【动态规划】【NOI2009】管道取珠

来源:互联网 发布:网络社区营销优点 编辑:程序博客网 时间:2024/04/29 22:48

【问题描述】管道取珠是小X很喜欢的一款游戏。在本题中,我们将考虑该游戏的一个简单改版。游戏画面如图1所示:


游戏初始时,左侧上下两个管道分别有一定数量的小球(有深色球和浅色球两种类型),而右侧输出管道为空。每一次操作,可以从左侧选择一个管道,并将该管道中最右侧的球推入右边输出管道。例如,我们首先从下管道中移一个球到输出管道中,将得到图2所示的情况。


假设上管道中有n个球, 下管道中有m个球,则整个游戏过程需要进行n + m次操作,即将所有左侧管道中的球移入输出管道。最终n + m个球在输出管道中从右到左形成输出序列。爱好数学的小X知道,他共有C(n+m, n)种不同的操作方式,而不同的操作方式可能导致相同的输出序列。举个例子,对于图3所示的游戏情形:

我们用A表示浅色球,B表示深色球。并设移动上管道右侧球的操作为U, 移动下管道右侧球的操作为D,则共有C(2+1,1)=3种不同的操作方式, 分别为UUD, UDU, DUU;最终在输出管道中形成的输出序列(从右到左)分别为BAB,BBA,BBA。可以发现后两种操作方式将得到同样的输出序列。假设最终可能产生的不同种类的输出序列共有K种,其中第i种输出序列的产生方式(即不同的操作方式数目)有ai个。聪明的小X早已知道,

因此,小X希望计算得到

你能帮助他计算这个值么?由于这个值可能很大,因此只需要输出该值对1024523的取模即可(即除以1024523的余数)。说明:文中C(n + m, n)表示组合数。组合数C(a, b)等价于在a个不同的物品中选取b个的选取方案数。【输入文件】输入文件ball.in第一行包含两个整数n, m,分别表示上下两个管道中球的数目。第二行为一个AB字符串,长度为n,表示上管道中从左到右球的类型。其中A表示浅色球,B表示深色球。第三行为一个AB字符串,长度为m,表示下管道中的情形。【输出文件】输出文件ball.out仅包含一行,即为

除以1024523的余数。【输入样例】2 1ABB【输出样例】5【样例说明】样例即为文中(图3)。共有两种不同的输出序列形式,序列BAB有1种产生方式,而序列BBA有2种产生方式,因此答案为5。【大致数据规模】约30%的数据满足 n, m ≤ 12;约100%的数据满足n, m ≤ 500。
朴素的搜索能过30分。
枚举每一次取出的小球(从上面取出,或者从下面取出),取出n + m个过后统计出每一种的个数,再将所有的平方相加即可。

#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <string>#define sqr(x) (int((long long)(x) * (x) % MOD))typedef long long int64;const int maxN = 20, MOD = 1024523;char up[maxN], dn[maxN], res[maxN << 1];int a[1 << 24], n, m, ans;inline void calc(){int tmp = 0;for (int i = 0, j = 1; i < n + m; ++i, j <<= 1)if (res[i] == 'B') tmp |= j;if (++a[tmp] >= MOD) a[tmp] -= MOD; return;}void Dfs(int i, int j){if (i >= n){strcat(res, dn + j); calc();for (int k = n + j; res[k]; ++k) res[k] = 0;return;}if (j >= m){strcat(res, up + i); calc();for (int k = i + m; res[k]; ++k) res[k] = 0;return;}res[i + j] = up[i]; Dfs(i + 1, j); res[i + j] = 0;res[i + j] = dn[j]; Dfs(i, j + 1); res[i + j] = 0;return;}int main(){freopen("ball.in", "r", stdin);freopen("ball.out", "w", stdout);scanf("%d%d%s%s", &n, &m, up, dn);std::reverse(up, up + n); std::reverse(dn, dn + m);Dfs(0, 0);for (int i = 0; i < 1 << (n + m); ++i)if ((ans += sqr(a[i])) >= MOD) ans -= MOD;printf("%d\n", ans); return 0;}
正解:动态规划。

直接入手似乎有些困难,不妨将原问题作一点转化:
输出第i种特定序列的方案数a[i]即为二元组(X, Y)(其中X, Y分别代表一种输出第i种序列的方式,且X与Y相互独立)的组数。
设X所对应的方案为(ix, jx)(从上管道取出了ix个,从下管道取出了jx个),Y所对应的方案为(iy, jy)(含义类似于ix, jx)。
于是,可以用状态f[ix][jx][iy][jy]作一下更新:
 1) 若上管道的第(ix + 1)个小球和上管道的第(iy + 1)个小球,那么更新状态f[ix + 1][jx][iy + 1][jy];
 2) 若上管道的第(ix + 1)个小球和下管道的第(jy + 1)个小球,那么更新状态f[ix + 1][jx][iy][jy + 1];
 3) 若下管道的第(jx + 1)个小球和上管道的第(iy + 1)个小球,那么更新状态f[ix][jx + 1][iy + 1][jy];
 4) 若下管道的第(jx + 1)个小球和下管道的第(jy + 1)个小球,那么更新状态f[ix][jx + 1][iy][jy + 1]。
实际上可以规定ix + jx = iy + jy,于是可以减少一维的状态。

Accode:

#include <cstdio>const int maxN = 510, MOD = 1024523;int f[maxN][maxN][maxN], n, m; char up[maxN], dn[maxN];inline void Add(int &a, int b){if ((a += b) >= MOD) a -= MOD; return;}int main(){freopen("ball.in", "r", stdin);freopen("ball.out", "w", stdout);scanf("%d%d%s%s", &n, &m, up + 1, dn + 1); f[0][0][0] = 1;for (int ix = 0; ix < n + 1; ++ix)for (int jx = 0; jx < m + 1; ++jx)for (int iy = 0; iy < n + 1; ++iy)if (f[ix][jx][iy]){int &t = f[ix][jx][iy], jy = ix + jx - iy;if (up[ix + 1] == up[iy + 1]) Add(f[ix + 1][jx][iy + 1], t);if (up[ix + 1] == dn[jy + 1]) Add(f[ix + 1][jx][iy], t);if (dn[jx + 1] == up[iy + 1]) Add(f[ix][jx + 1][iy + 1], t);if (dn[jx + 1] == dn[jy + 1]) Add(f[ix][jx + 1][iy], t);}printf("%d\n", f[n][m][n]); return 0;}

原创粉丝点击