Solution [NOI2009]管道取珠
题目大意:有黑球和白球排成两行,每次可以从任意一行排头取一个球放到输出序列尾部。设得到第 (i) 种输出序列的方案数为 (a_i),求 (sum a_i^2)
dp、计数
分析:
根据乘法原理,方案数的平方和,等于两个人独立地进行操作,然后他们得到相同输出序列的方案数。
那么我们就可以暴力设一个状态 (f[i][j][k][l]),表示两个人在上面下面分别取了多少个,显然这样会炸掉
但是发现他们输出序列相同的必要条件是 (i + j = k + l),就可以把状态优化为
(f[len][i][j]),表示总共取 (len) 个,两个人分别在上下取 (i,j) 个得到相同输出序列的方案数
转移十分容易,需要使用滚动数组滚掉 (len) 降低空间开销
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
constexpr int maxn = 512,mod = 1024523;
int f[2][maxn][maxn],n,m;
char str1[maxn],str2[maxn];
int main(){
scanf("%d %d
",&n,&m);
scanf("%s",str1 + 1);
scanf("%s",str2 + 1);
f[0][0][0] = 1;
for(int len = 0;len <= n + m;len++){
int now = len & 1;
memset(f[now ^ 1],0,sizeof(f[now ^ 1]));
for(int i = 0;i <= min(len,n);i++)
for(int j = 0;j <= min(len,n);j++){
if(f[now][i][j] >= mod)f[now][i][j] %= mod;
if(!f[now][i][j])continue;
if(str1[i + 1] == str1[j + 1])f[now ^ 1][i + 1][j + 1] += f[now][i][j];
if(str1[i + 1] == str2[len - j + 1])f[now ^ 1][i + 1][j] += f[now][i][j];
if(str2[len - i + 1] == str1[j + 1])f[now ^ 1][i][j + 1] += f[now][i][j];
if(str2[len - i + 1] == str2[len - j + 1])f[now ^ 1][i][j] += f[now][i][j];
}
}
printf("%d
",f[(n + m) & 1][n][n]);
return 0;
}