洛谷P1758 [NOI2009]管道取珠(dp 贡献转化)

题目

bzoj1566

两个管道的小球序列,

分别用长为n(n<=500)和长为m(m<=500)的仅由A和B构成的字符串表示

两个管道归并的时候,每次可以从上管道取一个球,也可以从下管道取一个球

假设最终可能产生的不同种类的输出序列共有K种,

其中第i种输出序列的产生方式(即不同的操作方式数目)有ai个。

聪明的小X早已知道,

因此,小X希望计算得到

你能帮助他计算这个值么?

由于这个值可能很大,因此只需要输出该值对1024523的取模即可(即除以1024523的余数)。

说明:文中C(n+m,n)表示组合数。组合数C(a,b)等价于在a个不同的物品中选取b个的选取方案数。

思路来源

https://www.luogu.com.cn/blog/user23116/solution-p1758 第一种dp

https://blog.csdn.net/pocket_lengend/article/details/79821748 第二种dp

题解

首先要理解一点,最终出现的每一个序列如果出现ai次,其贡献是ai^2

这等价于,对一个序列取两次,取两次的过程中出现两个序列相同的方案数,

设某个序列出现了a次,则在第一次里出现了a次,第二次里也出现了a次,

则对于该序列来说,第一次=第二次的方案数为a*a次,即a^2次,

理解了这一点后,有两种dp方式,

一种是dp[i][j][k][l]表示第一次第一个管道取了i个,第一次第二个管道j个

第二次第一个管道取了k个,第二次第二个管道取了l个的完全相同序列(p,q)对的方案数

把第四维优化掉就是dp[i][j][k],再把第一维滚动一下得到dp[2][505][505]

另一种是考虑总长,i+j=k+l,

所以dp[len][j][k]表示总长为len,第一次第一个管道取了j个,第二次第一个管道取了k个

的完全相同序列(p,q)对的方案数,第二种更便于写转移和理解,虽然看似多了一个二倍的常数

代码

#include<bits/stdc++.h>
using namespace std;
const int N=505,mod=1024523;
int n,m,dp[2][N][N];
char s[N],t[N];
void add(int &x,int y){
    x+=y;
    x%=mod;
}
int main(){
    dp[0][0][0]=1;
    scanf("%d%d",&n,&m);
    scanf("%s%s",s+1,t+1);
    for(int i=0,f=0;i<n+m;++i,f^=1){
        for(int j=0;j<=min(i,n);++j){
            for(int k=0;k<=min(i,n);++k){
                if(s[j+1]==s[k+1])add(dp[f^1][j+1][k+1],dp[f][j][k]);
                if(s[j+1]==t[i+1-k])add(dp[f^1][j+1][k],dp[f][j][k]);
                if(t[i+1-j]==s[k+1])add(dp[f^1][j][k+1],dp[f][j][k]);
                if(t[i+1-j]==t[i+1-k])add(dp[f^1][j][k],dp[f][j][k]);
                dp[f][j][k]=0;
            }
        }
    }
    printf("%d\n",dp[(n+m)&1][n][n]);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值