zoukankan      html  css  js  c++  java
  • 鸡蛋篮子与格子取数

    1.鸡蛋篮子

      有N个鸡蛋和M个篮子,把鸡蛋放到M个篮子里,每个篮子都不能为空。另外,需要满足:任意一个小于N的正整数,都能由某几个篮子内蛋的数量相加的和得到。写出程序,使得输入一个(N,M),输出所有可能的分配情况。

      思路:

      1.规定篮子鸡蛋数量从小到大递增枚举。

      basket[M]:M个篮子中的鸡蛋数量

      current_sum:当前所有篮子鸡蛋的总和,

      basket_id:当前篮子的序号,
      current_num:将要放到当前篮子去的鸡蛋数量,

      2.由题意:任意一个小于N的正整数,都能由某几个篮子内蛋的数量相加的和得到

      对于这M个篮子中的鸡蛋数量,我们用数组basket[M]来表示,所以:

      对于前n个篮子,其鸡蛋数量总和为Sn,那么对于第n+1个篮子,其鸡蛋数量应该满足:
         basket[n+1] <= Sn + 1,如果basket[n+1] > Sn + 1,那么Sn + 1这个数将无法通过相应的篮子鸡蛋数相加来获得。
        由于是非递减序列,因而 子问题之间关系
         basket[n] <= basket[n+1] <= Sn + 1

      3.剪枝要求:猜出最大 和 最小 ,其余不符合的剪掉

      最小:(current_sum + current_num*(M - basket_id)) > N

      因为是非递减序列,所以假设以后篮子全是当前篮子的个数,就是最小的情况,如果这样加起来也大于N,那么肯定不符。

      最大:(current_sum + 1)*((1<<(M - basket_id)) - 1) < N

      假设前面的篮子总和为n,那么紧挨着的后一个篮子里鸡蛋数量最大值为n+1,其后的一个篮子最大值为n + (n + 1) + 1 = 2n + 2,这之后的一个篮子的最大值为n + (n + 1) + (2n + 2) + 1 = 4n + 4......(即这里取的都是S+ 1)
      依次类推,我们发现n + 1 + (2n + 2) + (4n + 4) + ...... = (2^count - 1)*(n + 1),count表示相应的篮子数量。

      (由子问题之间关系归纳总结出整体规律

    N eggs M baskets
    #include <stdio.h>
    #include <stdlib.h>
    
    void solve(int current_sum, int basket_id, int current_num, int* basket, int N, int M)
    {
        if (current_sum == N && basket_id == M)
        {
            int i;
            for (i = 0; i < M; i++)
                printf("%d	", basket[i]);
            printf("
    ");
            return;
        }
    
        if (current_num > N || basket_id >= M)
            return;
    
        if ((current_sum + current_num*(M - basket_id)) > N || 
            (current_sum + (current_sum + 1)*((1<<(M - basket_id)) - 1)) < N)
            return;
    
        int j;
        for (j = current_num; j <= current_sum + 1; j++)
        {
            basket[basket_id] = j;
            solve(current_sum + j, basket_id + 1, j, basket, N, M);
        }
    }
    
    int main()
    {
        int N;//the number of eggs
        int M;//the number of baskets
        while (scanf("%d%d", &N, &M) != EOF)
        {
            if (N < M || N >= 1<<M || M <= 0)
                printf("Wrong data!
    ");
            else
                printf("The combinations are as below:
    ");
    
            int* basket = (int*)malloc(sizeof(int)*M);
            solve(0, 0, 1, basket, N, M);
            free(basket);
        }
        return 0;
    }

      2.格子取数问题

      题目详情:有n*n个格子,每个格子里有正数或者0,从最左上角往最右下角走,只能向下和向右,一共走两次(即从左上角走到右下角走两趟),把所有经过的格子的数加起来,求最大值SUM,且两次如果经过同一个格子,则最后总和SUM中该格子的计数只加一次。

      1) 贪心算法

      最初想到的思路可能是让每一次的路径都是最优的,即不顾全局,只看局部,让第一次和第二次的路径都是最优。

      但问题马上就来了,虽然这一算法保证了连续的两次走法都是最优的,但却不能保证总体最优,相应的反例也不难给出,请看下图:

      

      也就是说,上面图二中的走法太追求每一次最优,所以第一次最优,导致第二次将是很差;而图三第一次虽然不是最优,但保证了第二次不差,所以图三的结果优于图二。由此可知不要只顾局部而贪图一时最优,而丧失了全局最优。

      2)动态规划

      用DP[s,i,j]来记录2次所走的状态获得的最大值,其中s表示走s步,i和j分别表示在s步后第1趟走的位置和第2趟走的位置。

      0  1  2  3  4
      1  2  3  4  5
      2  3  4  5  6
      3  4  5  6  7
      4  5  6  7  8

      上述图中,数字代表的就是步数(第二次转换为从左上向右下走)

      0  0  0  0  0
      1  1  1  1  1
      2  2  2  2  2
      3  3  3  3  3
      4  4  4  4  4

      其中步数相同的时候,i,j的取值来自此图。

      由于走过的路只加一次值,所以DP[s,i,j]中,当i=j时,sum只加一次!

      W[s,i]表示经过s步后,处于i位置,位置i对应的方格中的数字。

      综上所述,递推公式为:

      if(i != j)
        DP[s, i ,j] = Max(DP[s - 1, i - 1, j - 1], DP[s - 1, i - 1, j], DP[s - 1, i, j - 1], DP[s - 1, i, j]) + W[s,i] + W[s,j]
      else
        DP[s, i ,j] = Max(DP[s - 1, i - 1, j - 1], DP[s - 1, i - 1, j], DP[s - 1, i, j]) + W[s,i]

      

    //为了便于实现,我们认为所有不能达到的状态的得分都是负无穷,参考代码如下:
    //copyright@caopengcs 2013
    const int N = 202;
    const int inf = 1000000000;  //无穷大
    int dp[N * 2][N][N];  
    bool isValid(int step,int x1,int x2,int n) { //判断状态是否合法
        int y1 = step - x1, y2 = step - x2;
        return ((x1 >= 0) && (x1 < n) && (x2 >= 0) && (x2 < n) && (y1 >= 0)                         
            && (y1 < n) && (y2 >= 0) && (y2 < n));
    }
    
    int getValue(int step, int x1,int x2,int n) {  //处理越界 不存在的位置 给负无穷的值
        return isValid(step, x1, x2, n)?dp[step][x1][x2]:(-inf);
    }
    
    //状态表示dp[step][i][j] 并且i <= j, 第step步  两个人分别在第i行和第j行的最大得分 时间复杂度O(n^3) 空间复杂度O(n^3) 
    int getAnswer(int a[N][N],int n) {
        int P = n * 2 - 2; //最终的步数
        int i,j,step;
        
        //不能到达的位置 设置为负无穷大
        for (i = 0; i < n; ++i) {
            for (j = i; j < n; ++j) {
                dp[0][i][j] = -inf;
            }
        
        }
        dp[0][0][0] = a[0][0];//初始值
    
        for (step = 1; step <= P; ++step) {
            for (i = 0; i < n; ++i) {
                for (j = i; j < n; ++j) {
                    dp[step][i][j] = -inf;
                    if (!isValid(step, i, j, n)) { //非法位置
                        continue;
                    }
                    //对于合法的位置进行dp
                    if (i != j) {
                        dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i - 1, j - 1, n));
                        dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i - 1, j, n));
                        dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i, j - 1, n));
                        dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i, j,n));
                        dp[step][i][j] += a[i][step - i] + a[j][step - j];  //不在同一个格子,加两个数
                    }
                    else {
                        dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i - 1, j - 1, n));
                        dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i - 1, j,  n));
                        dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i, j,  n));
                        dp[step][i][j] += a[i][step - i]; // 在同一个格子里,只能加一次
                    }
                    
                }
            }
        }
        return dp[P][n - 1][n- 1];
    }    

      3)动态规划 改进

      上述实现的代码的复杂度空间复杂度是O(n^3),事实上,空间上可以利用滚动数组优化,由于每一步的递推只跟上1步的情况有关,因此可以循环利用数组,将空间复杂度降为O(n^2)。

      即我们在推算dp[step]的时候,只依靠它上一次的状态dp[step - 1],所以dp数组的第一维,我们只开到2就可以了。即step为奇数时,我们用dp[1][i][j]表示状态,step为偶数我们用dp[0][i][j]表示状态,这样我们只需要O(n^2)的空间,这就是滚动数组的方法。

    参考:http://www.cnblogs.com/shuaiwhu/archive/2012/06/20/2555541.html

    http://www.cnblogs.com/v-July-v/p/3320870.html

  • 相关阅读:
    codeforces 645C. Enduring Exodus
    test markdown
    codeforces 817C Really Big Numbers
    797C C. Minimal string
    ubuntu配置安卓开发环境记录
    常用网址
    Mysql/Oracle/达梦中数据字典表
    Hadoop0.20.2中MapReduce读取gb2312文件出现乱码问题
    凝思磐石4.2系统字符编码的修改
    hadoop学习(三)HDFS常用命令以及java操作HDFS
  • 原文地址:https://www.cnblogs.com/jslee/p/3431791.html
Copyright © 2011-2022 走看看