[BZOJ1925][Sdoi2010]地精部落(抖动序列dp)

题目:

我是超链接

题解:

设计状态的话。。。

f[i][j]表示长度为i(可以理解为序列是1~i的排列)首项(强制是山峰)取值为[1,j]的方案数

转移方程就是:f[i][j]=f[i][j-1]+f[i-1][i-j]

step 1

首先来看f[i][j-1],直接累加首项为[1,j-1]的方案数

step 2

还有就是如何求首项为j的方案数呢

一般的,长度为i的状态都是由长度为i-1的状态转移来的

假设首项为j,依所设第一个必须是山峰,于是只需要算出首项取值为[1,j-1],长度为i-1,且第一个是山谷的序列的方案数即可, 记作g[i-1][j-1],【有点像把这些长度为i-1的序列粘在首项为j的后面(反正也大不过j,来了就当山谷呗)】

step 3

这个状态的另一个巧妙之处就是:我们强制第一个为山峰,其余的方案都可以通过取反得到
对于长度为i的数列,如果使数列的每一项ai变为i-ai+1,都可以得到恰好相反的一个数列,他们一一对应
【这个感觉就是把山谷变成了山峰,山峰变成山谷,方案数还是一样的啊】
于是

f[i][j]=g[i][i-j+1]

上面的g[i-1][j-1]=f[i-1][i-j],方程得解?

最后答案要*2(第一个还可以是山谷呢喂!)
要用滚动数组优化空间哦!

以下问题引自其他博主:

如果g[i-1][j-1]=f[i-1][i-j]含有数字j怎么办?
实际上只用在想象中将g[i-1][j-1]中大于等于j的数字全部加一,肯定能形成合法序列,并且也是一一对应的,因为ta们的相对大小并不发生变化,g的主要目的就是记录一个相对大小!

代码:

#include <cstdio>
#include <iostream>
using namespace std;
int n,mod,f[2][5000];
int main()
{
    int i,j;
    scanf("%d%d",&n,&mod);
    if (n==1){printf("1");return 0;}
    f[1][1]=1;
    for (i=2;i<=n;i++)
      for (j=1;j<=i;j++)
        f[i&1][j]=(f[i&1][j-1]+f[(i-1)&1][i-j])%mod;
    printf("%d",f[n&1][n]*2%mod);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值