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

来源:互联网 发布:淘宝带图评价福利 编辑:程序博客网 时间:2024/04/30 15:18

题目:

我是超链接

题解:

设计状态的话。。。

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);}
原创粉丝点击