NOI2009 第二场 B 题解
洛谷 P1758 管道取珠(ball)
题目大意
两个栈,自底向上分别有 \(n,m\) 个球,每个球都可能是 A、B 两种颜色之一。取 \(n+m\) 次球,每次取球从两个栈的栈顶弹出一个,先后排列,记录下颜色,则会产生长 \(n+m\) 的颜色序列。设一共可能产生 \(k\) 种不同的颜色序列,每种序列有 \(a_i\) 种取法,显然
\[\sum_{i=1}^ka_i=\binom{n+m}m,
\]
但是我们要求
\[\sum_{i=1}^ka_i^2
\]
对 \(1~024~573\) 取模的结果。
数据范围
- \(1\le n,m\le 500\)。
思路
既然 \(a_i\) 是取一次的结果,那么 \(a_i^2\) 可不可以看成取两次呢?我们这样想:把两个栈复制一份,让两个人分别去取,每个人有 \(a_i\) 种取法,那么合到一起就是 \(a_i^2\) 种情况了。考虑 DP:设 \(f_{i,j,k}\) 为,两个人都拿了 \(i\) 个球,其中,第一个人从第一个栈拿了 \(j\) 个球,第二个人从第一个栈拿了 \(k\) 个球,此时两个人拿球的序列相同的方案数。
那么,显然只有栈顶颜色一样的情况才能转移。我们令 \(A_i,B_i\) 分别为两个栈自顶向下第 \(i\) 个球的颜色,开始转移:
\[\begin{aligned}
f_{i,j,k}&=f_{i-1,j-1,k-1}\times[j\ge1\wedge k\ge1\wedge A_j=B_k]\\
&+f_{i-1,j,k-1}\times[i-j\ge1\wedge k\ge1\wedge B_{i-j}=A_k]\\
&+f_{i-1,j-1,k}\times[j\ge1\wedge i-k\ge1\wedge A_j=B_{i-k}]\\
&+f_{i-1,j,k}\times[i-j\ge1\wedge i-k\ge1\wedge B_{i-j}=B_{i-k}],
\end{aligned}
\]
其中中括号为艾弗森括号,\(\wedge\) 相当于 C++ 中的 && 或 and。如果看晕了,就看最后一个 \(\wedge\) 后面那坨,前面都是判边界的。初始状态是
\[f_{0,0,0}=1,
\]
最终答案为
\[f_{n+m,n,n}.
\]
时间复杂度 \(O(n^3+mn^2)\approx O(n^3)\)。
AC 代码
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int mod = 1'024'523;
char a[505],b[505];
long long ZRX[2][505][505];
#define f(i,j,k) ZRX[(i)&1][j][k]
int main() {
int n,m; scanf("%d%d",&n,&m);
scanf("%s%s",a+1,b+1);
reverse(a+1,a+n+1);
reverse(b+1,b+m+1);
f(0,0,0) = 1;
for (int i = 1; i <= n+m; i++) {
for (int j = 0; j <= n; j++)
for (int k = 0; k <= n; k++)
f(i,j,k) = 0;
for (int j = 0; j <= n; j++)
for (int k = 0; k <= n; k++) {
if (j >= 1 && k >= 1 && a[j] == a[k])
f(i,j,k) = (f(i,j,k)+f(i-1,j-1,k-1))%mod;
if (i-j >= 1 && k >= 1 && b[i-j] == a[k])
f(i,j,k) = (f(i,j,k)+f(i-1,j,k-1))%mod;
if (j >= 1 && i-k >= 1 && a[i] == b[i-k])
f(i,j,k) = (f(i,j,k)+f(i-1,j-1,k))%mod;
if (i-j >= 1 && i-k >= 1 && b[i-j] == b[i-k])
f(i,j,k) = (f(i,j,k)+f(i-1,j,k))%mod;
}
}
printf("%lld",f(n+m,n,n));
return 0;
}

浙公网安备 33010602011771号