求出入栈操作种类(动态规划算法)

来源:互联网 发布:知世故而不世故 圆滑 编辑:程序博客网 时间:2024/05/22 01:29

昨天参加九度上的王道考研机试练习赛,结果惨不忍睹,四道题做出两道,而且最后一道还因为时间到了来不及提交。今天特来研究昨天难住我的题,先从第二题出入栈开始吧。

题目描述:

给定一个初始为空的栈,和n个操作组成的操作序列,每个操作只可能是出栈或者入栈。要求在操作序列的执行过程中不会出现非法的操作,即不会在空栈时执行出栈操作,同时保证当操作序列完成后,栈恰好为一个空栈。求符合条件的操作序列种类。例如,4个操作组成的操作序列符合条件的如下:入栈,出栈,入栈,出栈入栈,入栈,出栈,出栈共2种。

输入:

输入包含多组测试用例,每组测试用例仅包含一个整数n(1<=n<=1000)。

输出:

输出仅一个整数,表示符合条件的序列总数,为了防止总数过多超出int的范围,结果对1000000007取模(mod 1000000007)。

样例输入:
2410
样例输出:
1242
来源:
2014年王道论坛研究生机试练习赛(一)
看到这道题的第一感觉就是找出一个数学公式可以直接求,或者用动态规划慢慢求,但是,想了很久也没找出规律可以用于公式求解,于是开始寻找可以用于动态规划的状态。当然,最直接的想法就是根据n-2次操作的种类数求出n次操作的种类数(注意到奇数次操作的情况会导致最后栈非空,不符合题意)。试想,已知n-2次操作(其中入栈出栈各一半)的种类数,再加一次入栈和出栈,会增加多少种?这会随着n不同而不同,至少我没想到状态转移时的固定关系。于是,使出了下下策,用深度优先搜索遍历解空间,列出所有出栈入栈的情况,显然这种算法的时间复杂度远高于公式求解和动态规划,并且递归调用函数需要栈空间,一旦n稍微大一点一定会超时,但当时情况紧急,我迫不得已还是使用了这种方法,题中给出的用例都可以算出来,但九度上的五个用例全部WA,说明测试点都是比较大的数据,至少大于24,因为在本地24是可以算出来的。虽然这题不能用这种方法,但也不失为一种思路,故贴出代码如下:

#include <stdio.h>int cnt,n;void next(int st,int step){    if (step==n)    {        if (st==0) ++cnt;        return;            }    else    {        ++step;        if (st>0)           next(st-1,step);        next(st+1,step);        --step;    }}int main(){    while (scanf("%d",&n)!=EOF)    {           int st=0, step=0;           cnt=0;           next(st,step);           printf("%d\n",cnt);    }    return 0;}



今天看了别人AC的代码,发现这道题确实可以用动态规划做,只是我没有选好合适的状态。顺着之前的思路,有n-2到n,每次增加一个入栈和一个出栈操作,以此作为状态转移无法得出结果,但如果以一次操作为单位进行状态转移,就会变得容易许多。我们以一个数对表示入栈操作数和出栈操作数,如(x,y)表示共进行x次入栈和y次出栈,那么y必不大于x,要求(x,y)情况下操作序列的种类数,我们只需知道(x,y-1)和(x-1,y)时的种类数,前者在各可能序列最后加上一次出栈操作,后者在各可能序列最后加上一次入栈操作,即得出(x,y)时的种类数。下面通过几个例子说明这一点:

出入栈数    序列数    序列(I为入栈,O为出栈)

(1,0)          1          I

(1,1)          1          IO

(2,0)          1          II

(2,1)          2          IOI IIO

(2,2)          2          IOIO IIOO

(3,0)          1          III

(3,1)          3          IOII IIOI IIIO

由以上很容易看出状态转移的关系。附上AC代码:

#include <stdio.h>#define MOD 1000000007int dp[510][510];                   //用二维数组表示一个数对对应情况的序列种类数int main(){    for (int i=0;i<501;++i)        for (int j=0;j<501;++j)            dp[i][j]=0;             //初始化dp数组    dp[1][0]=1;                      dp[1][1]=1;    for (int i=2;i<501;++i)    {        for (int j=0;j<=i;++j)        {            if (j==0) dp[i][j]=1;   //当没有出栈操作的时候只有一种序列            else                    //状态转移            {                dp[i][j]=dp[i][j-1]+dp[i-1][j];                dp[i][j]%=MOD;            }        }    }    int n;    while (scanf("%d",&n)!=EOF)    {        if (n&1==1) printf("0\n");        else printf("%d\n",dp[n/2][n/2]);    }    return 0;}


0 0
原创粉丝点击