[BZOJ 1056][NOI 2009]管道取珠(DP)

142 篇文章 0 订阅
59 篇文章 0 订阅

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=1566

思路

设上面的珠子序列是U,下面的是L,定义一个取珠子的操作序列的状态(i,j)表示上面的U取了i个,下面的L取了j个。那么 a2i= 对于两个不同的操作序列 XY ,它们最后取出的珠子序列是一样的,这样的有序对 (X,Y) 的个数。证明如下:
假如最终取出的珠子序列为 A ,它对应的操作序列个数为|Z|,也就是说 ai=|Z|2 ,那么根据分步计数原理,上面说的 (X,Y) 的对数显然等于 |Z|2 ,也就是 ai=|Z|2 ,得证。

那么这个 (X,Y) 的对数也很好求。用 f[i1][j1][i2][j2] 来表示操作序列 X 的状态为(i1,j1),操作序列 Y 的状态为(i2,j2),并且当前 XY 操作完以后取出来的珠子序列仍然是一样的 (X,Y) 方案数(就是按照上面说的来的)。DP方程也很好推,当前状态转移到下一状态后,两个操作序列各自增加的那个新柱子的颜色必须相同,DP方程如下。
这里写图片描述
以上图片转自https://www.byvoid.com/blog/noi-2009-ball/

最后的那个 j2 下标完全可以从前三个下标推出来,因此DP方程和DP过程都是三维的复杂度。

后记:
虽然这个题并不难,但是我WA了十多次,具体有很多细节需要注意:
1、输入了 UT 序列后,记得把它们翻转一下,因为我们定义的输入的珠子序列,出口处的柱子在下标1的位置。
2、注意最后一个下标 j2 是从前三个下标推出来的,要判断 j2 是否合法。

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 505
#define MOD 1024523

using namespace std;

int n,m;
//注:状态(i,j)表示上面的U取了i个,下面的L取了j个
int f[MAXN][MAXN][MAXN]; //f[i1][j1][i2][j2]=X方法当前状态为(i1,j1),Y方法当前状态为(i2,j2),这样的有序对(X,Y)个数,j2=i1+j1-i2,因此j2状态可以略去
char U[MAXN],L[MAXN];

int add(int x,int y)
{
    x+=y;
    if(x>MOD) x-=MOD;
    return x; //!!!!
}

int main()
{
    scanf("%d%d",&n,&m);
    scanf("%s+1",U+1); //!!!!
    scanf("%s+1",L+1); //!!!!
    reverse(U+1,U+n+1); //!!!!
    reverse(L+1,L+m+1); //!!!!
    f[0][0][0]=1;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=m;j++)
            for(int k=0;k<=n;k++)
            {
                int tmp=f[i][j][k],l=i+j-k; //X状态为(i,j),Y状态为(k,l)
                if(!tmp||l<0||l>m) continue;
                if(U[i+1]==U[k+1])
                    f[i+1][j][k+1]=add(f[i+1][j][k+1],tmp);
                if(U[i+1]==L[l+1])
                    f[i+1][j][k]=add(f[i+1][j][k],tmp);
                if(L[j+1]==U[k+1])
                    f[i][j+1][k+1]=add(f[i][j+1][k+1],tmp);
                if(L[j+1]==L[l+1])
                    f[i][j+1][k]=add(f[i][j+1][k],tmp);
            }
    printf("%d\n",f[n][m][n]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值