BZOJ 1925 [Sdoi2010]地精部落

抖动序列DP

思维难度好大- -,我就发一个比较详细的题解吧

记f[i][j]表示长度为i(可以理解为序列是1~i的排列),首项取值为[1,j]的第一段为下降抖动序列方案数。(这个方程还真是很难想出来,要考虑到长度为i的序列可以由长度为i-1的序列通过首项的关系转移过来)

方程:f[i][j]=f[i][j-1]+f[i-1][i-j]

f[i][j-1]好理解,这可以直接求出首项为[1,j-1]的方案数,于是接下来要考虑的是如何求首项为j的方案数。

假设首项为j,依所设第一段下降,第二段必上升,于是只需要算出首项取值为[1,j-1],长度为i-1,且第一段上升的序列的方案数即可, 记作g[i-1][j-1]。

发现一个性质,对于长度为i的数列,如果使数列的每一项ai变为i-ai+1,都可以得到每一段状态(上升或下降)恰好相反的一个数列,并且他们一定是一一对应的,于是有f[i][j]=g[i][i-j+1],于是上面的g[i-1][j-1]=f[i-1][i-j],方程就不难理解了。

有一个不好理解的地方,对于g[i-1][j-1]的任一序列里面的数和f[i][j]的首项相等,即含有数字j怎么办?我们只需要使g[i-1][j-1]中大于等于j的数字全部加一再加上新首项j,肯定能形成合法序列,并且也是一一对应的。

注意,f[i][j-1]≠g[i][j-1],因为即使具有对称性,但f中首项虽取值[1,j-1],但取上一段所述的对称操作后不一定会小于j,所以不能用它转移。为什么我要说这个,因为我之前想的时候用的是这个,结果各种不对。。。

#include<cstdio>
int f[2][4205];
int main()
{
    int n, p, cur=1;
    scanf("%d%d",&n,&p);
    if(n==1)return !printf("%d\n",1%p);
    f[cur^1][1]=1;
    for(int i = 2; i <= n; i++, cur^=1)
        for(int j = 1; j <= i; j++)
            f[cur][j]=(f[cur][j-1]+f[cur^1][i-j])%p;
    printf("%d\n",f[cur^1][n]*2%p);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值