【问题描述】
管道取珠是小X很喜欢的一款游戏。在本题中,我们将考虑该游戏的一个简单改版。游戏画面如图1所示:
游戏初始时,左侧上下两个管道分别有一定数量的小球(有深色球和浅色球两种类型),而右侧输出管道为空。每一次操作,可以从左侧选择一个管道,并将该管道中最右侧的球推入右边输出管道。
例如,我们首先从下管道中移一个球到输出管道中,将得到图2所示的情况。
![](https://img-my.csdn.net/uploads/201205/16/1337138666_9100.png)
假设上管道中有n个球, 下管道中有m个球,则整个游戏过程需要进行n + m次操作,即将所有左侧管道中的球移入输出管道。最终n + m个球在输出管道中从右到左形成输出序列。
爱好数学的小X知道,他共有C(n+m, n)种不同的操作方式,而不同的操作方式可能导致相同的输出序列。举个例子,对于图3所示的游戏情形:
![](https://img-my.csdn.net/uploads/201205/16/1337138677_5833.png)
我们用A表示浅色球,B表示深色球。并设移动上管道右侧球的操作为U, 移动下管道右侧球的操作为D,则共有C(2+1,1)=3种不同的操作方式, 分别为UUD, UDU, DUU;最终在输出管道中形成的输出序列(从右到左)分别为BAB,BBA,BBA。可以发现后两种操作方式将得到同样的输出序列。
假设最终可能产生的不同种类的输出序列共有K种,其中第i种输出序列的产生方式(即不同的操作方式数目)有ai个。聪明的小X早已知道,
![](https://img-my.csdn.net/uploads/201205/16/1337138688_2767.png)
因此,小X希望计算得到
![](https://img-my.csdn.net/uploads/201205/16/1337138697_5989.png)
你能帮助他计算这个值么?由于这个值可能很大,因此只需要输出该值对1024523的取模即可(即除以1024523的余数)。
说明:文中C(n + m, n)表示组合数。组合数C(a, b)等价于在a个不同的物品中选取b个的选取方案数。
【输入文件】
输入文件ball.in第一行包含两个整数n, m,分别表示上下两个管道中球的数目。
第二行为一个AB字符串,长度为n,表示上管道中从左到右球的类型。其中A表示浅色球,B表示深色球。
第三行为一个AB字符串,长度为m,表示下管道中的情形。
【输出文件】
输出文件ball.out仅包含一行,即为
![](https://img-my.csdn.net/uploads/201205/16/1337138704_5058.png)
除以1024523的余数。
【输入样例】
2 1
AB
B
【输出样例】
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;
}