ACM动态规划系列

来源:互联网 发布:马哥linux培训怎么样 编辑:程序博客网 时间:2024/04/28 19:09
/** *1 首先为什么要用动态规划, *答:这是基于对问题的分析,一个问题可以分割成多个规模更小的子问题, *其实也就是说这个题目如果那个变量更小时,这个问题怎么分析基本没变 *那么这个变量就是一维数组中的坐标 Calc[i], 与之相关的是,题目的解往往 *会跟着这个自变量一起移动,那题目的解就是 [ Cala[i] ]; *之后就是写递归方程,这个如果对问题的变量分析正确的话,基本能写出来 *之后就是自底向上求解 *首先考虑的是,特殊的值,这个一般都是循环的起点; *例如 Calc[0] = 0; *例如本题中 Calc[i] = min{ Calc[temp] + Coin[j].weight }; * 这里定个规则, * i 总是写在最内层, 影响 解的变量写在外层。 * 例如本例中。 * Weight[1- N]; N types coin, N weights * i kilogramer * so the cycle is  * for( int v1 =0; v2 < N; v1++) *    for( int i= 0; i< MAXWEIGHT; i++) *    if( Calc[i] != IMAX && i+ Weight[v1] <= MAXWEight && Value[ i + Weight[v1] ] > Value[i] + v[ v1] ) *                Value[i + Weight[v1] ] = Value[i] + V[ v1 ]; * * * 

 */

POJ1384

#include <stdio.h> #include <string.h> #define MAX 0x11111111  int Bag[10010], Weight[501], Value[501], n;  int main() { int t; scanf("%d", &t); int a, b, plus; while( t-- ) { memset(Bag, 0x11, sizeof(Bag) ); scanf("%d%d%d", &a, &b, &n); for(int i = 0; i< n; i++) scanf("%d%d", &Weight[i],&Value[i]); plus = b- a; if( plus < 0 ) plus = -plus; Bag[0] = 0; for( int i= 0; i< n; i++) for( int j= 0; j<= plus; j++) if( Bag[j] != MAX && j+ Weight[i] <= plus &&  ( Bag[ j+Weight[i] ] > Bag[j] + Value[i] ) ) Bag[ j+Weight[i] ] = Bag[j] + Value[i];  if( Bag[plus] != MAX) printf("The minimum amount of money in the piggy-bank is %d\n.",Bag[plus]); else puts("This is impossible.");  }     return 0; }

/**
 *对于给定的n 个序列,S1,S2,。。。Sn, 编程计算其最长公共子序列的长度
 *LCS:
 *注意此处的最长公共子序列不一定是连续的,连续的就用KMP算法啊。
 *解法就是用一个矩阵来记录两个字符串中所有位置的两个字符之间的匹配情况
 *若是匹配为1,否则为0, 然后球出对角线最长的1序列,其对应的位置就是最长
 *匹配字串的位置
 *当字符串匹配时,我们并不是简单的给相应元素附上1, 而是其左上角元素的值加一。
 *我们用两个标记变量来标记矩阵中值最大的元素的位置,在矩阵生成的过程
 *中判断当前生成的元素是不是最大的,到矩阵完成,答案也就出来了。
 *C[i][j] 保存字符串 { S1, S2} 部分子串(i, j) 的最长公共字串的字符个数
 * i== 0 || j== 0  C[i][j] = 0; 若 S1[i] == S2[j]  C[i][j] = C[i-1][j-1]+1;
 * if S1[i] != S2[j]  C[i][j] = max{ C[i-1][j] , C[i][j-1] };
 *
 */

#include <sdtio.h>#include <string.h>int C[1001][1001];int  LCS(int Len1, int Len2 , char S1[], char S2[] ){memset( C, 0, sizeof(C) );int i, j;for( i = 1; i<= Len1; i++)for( j= 1; j< len2; j++){if( S1[i-1] == S2[j-1] )C[i][j] = C[i-1][j-1] +1;else{if( C[i-1][j] >= C[i][j-1] )C[i][j] = C[i-1][j];elseC[i][j] = C[i][j-1];}}printf("%d\n", C[len1][len2]);}

/**
 * 求一个给定的子符串至少添加几个字母使之变成回文串
 * 记原字符串是a, 另其反转串为 F(a) = b; 找到 a, b 两个字符串的关系,
 * 就是求 a,b 之间的最长公共子序列(LCS),如果a, b 的LCS 的长度为len
 * then the answer is strlen(a) - LCS;
 * 注意是怎么证明的,并且如果构造最短的回文串。这个算法太妙了
 * 具体代码不再写,参照上面的代码,最小的完整的回文序列也差不多。
 */
 



/**
 * 二维DP
 * 二维DP中最著名的肯定是背包问题,本例中将讲解2个背包问题,用来加深对二维背包问题的理解。
 * 1 完全背包问题:
 * 有N种物品和一个容量为H的背包,每种物品都有无限的数量可用,第i种物品的费用是W[i],
 * 价值是V[i], 求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值最大
 * 思考:首先问:这个适合用DP求解吗?显然是适合的。 那么我们需要现描述问题的状态。
 * 这个问题的自变量有哪些, 容量h, 第i种物品取0 ,1,2,3,所以这里的第二个自变量
 * 不能是第i种商品,而是前i种商品,那么因变量自然是 价值
 * DP[i][H] 表示前i种商品恰好放入一个容量是H的背包的最大价值。
 * 方程: DP[i][h] = max{ DP[i-1][h-K*W[i] ] + K*V[i] } while 0<= K*W[i] <= h
 * 这个方程的意思就是,从DP[i-1] -> DP[i]的状态就是加上若干个物品i
 * 复杂符分析 总共有 O(NH)个状态, 这其中每个状态的求解时间是 O(h/C[i]),
 * 复杂读还是挺高的。
 * 优化,前面的问题在于DP[i] 与 DP[i-1] 相差多个物品 i;
 *    优化就是把递归状态之间相差一个
 *  DP[i][h] = max{ DP[i-1][h], DP[i][h-W[i] ] + V[i] }
 *  这个方程的意思就是DP[i][h] ,要么不要第i个物品,要么就是多加一个物品i;
 *  这个复杂符就是 O(NH).
 */
 
 


/**
 *    多重背包问题:
 *  题目有N种和一个容量为H的背包,第i种物品最多有N[i]个可用。每个物品重量W[i],
 *  价值W[i],求解将哪些物品装入背包可使这些物品的总重量不超过H, 且价值最大。
 *  DP[i][h] = max{ DP[i-1][h- k*W[i] ] + K*V[i] } while 0<= K<= n[i] && h-K*W[i] >=0
 *  由前面分析可知,复杂度较高。
 *  优化,转化成01背包问题, 就像前面所说的,减少一个状态到另一个状态
 *  二进制优化,第i种物品 有 N[i] 种, 拆分成logN[i]种,举例,7 个物品 i
 *  转化成 (4个物品i), (2个物品i), (1个物品i) 把括号内的都看成单个物体。
 *  这个复制度是有优化的, O( H* Sum( log(N[i]) ) );
 *
 */



/**
 * 树形DP
 * 以具体实例:POJ2378
 * 给定一颗n个节点的树,如果删去某个节点,使得剩下来的分支中,节点数最多的分支的节点
 * 小于等于节点总数的一半。那这个节点就是可行的,找出所以这样的节点:Input
 * Line 1: A single integer, N. The barns are numbered 1..N.
 * Lines 2..N: Each line contains two integers X and Y and represents a connection   
 * between barns X and Y.
 *
 * 首先要解决的问题是树的结构的处理,这是一个无根树。
 * 一般而言,我的处理方式是 Vector<int> Tree[NUM];
 * 如果一颗树枝的描述是 1 2
 * 那么就是Tree[1].push_back(2);
 * Tree[2].push_back(1); 但有时候也使用二维数组,因为Vector 会自动扩容,对内存利用不好计。
 *    但是这题好像没怎么用到DP的思想,就是 DP[i] 表示以节点i为根的子树的节点数目。
 * 那么关键的问题就是怎么求结点i 为根的子树的数目。
 *
 */
 //第i个子树 的节点的数目, 需要外层变量Used[],来标记已经访问过的结点。
 //Used[i] 的作用是我们规定, Tree[a].push_back(b), Tree[b].push_back(a);
 //但实际上,我们把 a 看作根节点,而b看作子节点,如果 a < b的话
 //这个变量的意思就是,我在遍历b的子树的时候,当我发现一个节点 Used[a] == 1
 //就说明a 是 b的爸爸,那么肯定在计数的时候不考虑a 啦。设想一下,一个节点
 //被访问第一次,肯定是他的爸爸访问的,由于树的直接前继只有一个,所以下一次
 //被访问肯定是他的儿子。

void subTree(int i) { int v1 = Tree[i].size(); DP[i] = 1; Used[i] = 1; int v2; int child; for( v2 = 0; v2 < v1; v2++) { child = Tree[i][v2]; if( Used[child] == 1)     continue; subTree(child); DP[i] += DP[child]; } }

//这里面有一点动态归划的思想的,就是保存各中间结果。
 //那么在这里,是去除一个节点,试想,取出一个节点
 //对它的爸爸来说就是少了一部份子树有影响,但是对它的儿子来说
 //没有影响,我这里说的是Split[i], 不再是以节点i为根的子树的
 //多少,而是,去除节点i后,所形成的各个部分,结点个数最多的子树的节点数目
 //有点绕,但也就两种情况,1 是原大树去除节点i为根的子树部分,2 是节点i的各个子树。
 // 这里的Used 需要 memset(Used, 0, sizeof(Used) );
 //SUm 是树中结点数目的总数。
 //Answer[i] 表示满足条件的 节点i

void Split(int i, int Sum) { int v1 = Tree[i].size(); int v2; int child; Split[i] = 0; Used[i] = 1; for( v2 =0; v2< v1; v2++) { if( Used[v2] == 1) { if( Split[i] < ( Sum- DP[i] ) ) { Split[i] = Sum - DP[i]; }//这是我上面所说的第一种情况。 } else { if( Split[i] < DP[v2] ) Split[i] = DP[v2];  //这是我所说的第二种情况 Split( V2, Sum); } }  if( Split[i] <= Sum/2 ) Answer[i] = 1;//初始化是0;局部可以优化。  } 

//这个题目到底为止算是分析清楚了吧,不过我觉得这个和传统的动态规划真是没有太大的联系。
 //还有更加复杂的树形DP, POJ1741就不讲了,太难,不会写。
 //与此类类似者还有POJ1655 POJ3140.。。。

之后还会新增一些代码
                


0 0
原创粉丝点击