SDOI2010地精部落

code如此简单,但是。。。思维量竟然!!!

题目大意:求长度为n的抖动子序列的种类数。

公式:            /   0                           (i<=0||i>n||j<=0||j>i)

          f[i][j]=|

                     \ f[i][j-1]+f[i-1][i-j]     (i>0&&i<=n&&j>0&&j<=i)

         (f[i][j]意义同下)

思路:抖动子序列里面有一个很好的性质:对于等位区段上升和下降的一一对应。且位置上的数如果用1...n表示的话,就是对称的。

        我们用f[i][j]表示长度为i开头是j的第一段下降的种类数。初始化f[2][2]=1。

        分情况:1)如果我们第二位取j-1,那么就是长度为i-1,开头是j-1的第一段上升序列的种类数,根据之前的性质,就可以把它转化为以(i-1)-(j-1)+1开头的长度为i-1的第一段下降的序列,也就是f[i-1][i-j+1];

       2)如果我们第二位不取j-1,那么就是f[i][j-1],(这个比较好理解,就是相当于前缀和,f[i][1]+f[i][2]+...+f[i][j-2],因为我们一直是这样累加上来的,所以就是f[i][j-1]),如果证明的话就。。。

        那么我们得到f[i][j]=f[i][j-1]+f[i-1][i-j+1],这样f[n+1][n+1]*2就是答案了(之所以是n+1,因为我们把第一位看做n+1,第二位才能放1~n的数,这样我们得到的相当于长度为n开头1~n的第一段上升的序列,根据性质,*2就是答案了。)

        关于公式里面是f[n][n]代表答案的,其实就是把f[i][j]=f[i+1][j+1]了,那么中间的[i-j+1]也就变成了[i-j],初始化也变成了f[1][1]=1。

        因为可能卡内存,所以我们用滚动数组,比较巧妙也很简单。

#include<iostream>

#include<cstdio>

using namespace std;

int f[2][5000]={0};

int main()

{

    freopen("sdoi10goblin.in","r",stdin);

    freopen("sdoi10goblin.out","w",stdout);

    

    int i,j,kind,n,p;

    scanf("%d%d",&n,&p);

    f[0][2]=1;

    for (i=3;i<=n+1;++i)

    {

        kind=i&1;

        for (j=1;j<=i;++j)

        {

          f[kind][j]=(f[kind][j-1]+f[!kind][i-j+1])%p;

       }

    }

    printf("%d\n",(2*f[kind][n+1])%p);

    

    fclose(stdin);

    fclose(stdout);

}
View Code

 

你可能感兴趣的:(sd)