zoukankan      html  css  js  c++  java
  • 【算法学习笔记】27.动态规划 解题报告 SJTU OJ 1254 传手绢

    Description

    活动的时候,老师经常带着同学们一起做游戏。这次,老师带着同学们一起传手绢。

    游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着手绢,当老师吹哨子时开始传,每个同学可以把手绢传给自己左右的两个同学中的一个(左右任意),当老师在此吹哨子时,游戏停止,此时,拿着手绢的那个同学要给大家表演一个节目。

    abc提出一个有趣的问题:有多少种不同的传手绢方法可以使得从abc手里开始传的手绢,传了m次以后,又回到abc手里。两种传手绢方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接手绢顺序组成的序列是不同的。比如有三个同学1号、2号、3号,并假设abc为1号,手绢传了3次回到abc手里的方式有1->2->3->1和1->3->2->1,共2种。

    Input Format

    共一行,有两个用空格隔开的整数n,m(3<=n<=30,1<=m<=30)。

    Output Foramt

    共一行,有一个整数,表示符合题意的方法数。

    Sample Input

    3 3 
    

    Sample Output

    2
    

    Hint

    40%的数据满足:3<=n<=30,1<=m<=20 100%的数据满足:3<=n<=30,1<=m<=30

    此问题很容易让人联想到邻接矩阵乘法含义。wiki了一下,这类问题叫做”传球问题“  

    证明在这里  矩阵乘法确实好理解 但是实际应用还是稍微复杂,

    这里先用一个递归方案来把这个问题进行解析。

    注意到一个人传球,只能传左一位和右一位,根据这个突破点就可以写成递归函数。

    /*分治法
    返回 一共n个人 还剩m次传球机会 最终传到第k个人手里的路线数
    k是当前拿球的人的编号 1号是最开始拿球的人
    */
    int passGame(int k,int n, int m){
        if(m==0)
            //若还剩0次传球机会 整好到了原处 那就返回1 表示原来的路线是对的 否则是0
            return (k==1) ? 1 : 0;
        m--;//开始传球了 所以要减少一次传球机会
        //向+1传的话
        int ans1 = passGame((k==n ? 1 : k+1 ),n,m);
        //向-1传的话
        int ans2 = passGame((k==1 ? n : k-1 ),n,m);
    
        return ans1+ans2;
    
    }    
    分治法

    然后调用passGame(1,n,m)即可

    会超时,这种分治法的解决方案是从顶至底的。它会有很多重复的计算,所以我们就会想到和分治法路线相反的动态规划。

    动态规划的一个核心思想就是将重复子问题提前计算,从而减少计算量。这也就意味着它必须从底向上计算。

    在这个背景下,我们可能直觉想到的是设置一个dp[n][m] 其中d[n][m]表示n个人 传m次 回到原点的方案数,

    然后想办法建立起 d[n][m]和d[n][m-1]的关系 或者和 d[n-1][m]的关系 或者和d[n-1][m-1]的关系,从而实现把这个问题分成若干个子问题来进行DP。

    但是发现找到他们之间的关系,非常困难。

    我们试着换一个思路,由分治法的思维,我们知道了一个事情就是 还剩s次传球机会时,球传到第k个人手里的路线数 = 还剩s-1次传球机会时,球传到第k+1个人手里的路线数 + 还剩s-1次传球机会,球传到第k-1个人手里的路线数

    PS s-1的原因是最后还要耗费一次传球机会来把球传到k手里 k+1 k-1 只是简单的表示k的左右

    那么我们就可以考虑构造一个dp[][]来存储还剩m次机会 传到第k个人手里的路线数目 这个dp方案就是把分治法给颠倒了而已 思路是完全一模一样的

    //动态规划算法
    int dp[35][35]={0};
    
    /*dp[i][j]表示 传i次球 传到第j个人的路线的个数
    起始点和终止点都是为1号人
    */
    int dp_game(int n,int m){
        dp[0][1]=1;//传0次 传到1号人手中 路线只有一个
        //开始找状态转移方程 
        /*其实想法很简单 就是想d[i][j]是从哪些状态过来的 
            d[i][j]应该是d[i-1][j-1]和d[i-1][j+1]来的 因为j只能收到j-1和j+1的球
            而每次传球恰好i+1
        传0次球 只有1号人才能接到球 其他人都是0 不用算
    
        */
        for (int i = 1; i <= m; ++i){
            for (int j=1; j <= n; ++j){
                dp[i][j] = dp[i-1][j==1 ? n: j-1 ] + dp[i-1][j==n? 1:j+1];
            }
        }
        return dp[m][1];
    }
    动态规划
  • 相关阅读:
    扩展GridView控件增加选择列
    Flash图片轮换/切换左右推拉效果
    Flash图片轮换/切换普通效果1
    WebForm_PostBackOptions未定义 解决方法
    SQL Server 2008无法还原日志备份或差异备份的问题解决(转)
    一个简单的C#多线程间同步的例子.[转]
    SQL2008报错:无法还原日志备份或差异备份,因为没有文件可用于前滚(转)
    C# 字符、字符串过滤,只能输入数字、中文、英文、大写、小写(转)
    C# Monitor类锁定对象
    使用SqlServer中的float类型时发现的问题(转)
  • 原文地址:https://www.cnblogs.com/yuchenlin/p/sjtu_oj_1254.html
Copyright © 2011-2022 走看看