鸡蛋篮子与格子取数
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......(即这里取的都是Sn + 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\t", basket[i]); printf("\n"); 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!\n"); else printf("The combinations are as below:\n"); 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