动态规划

广泛用于求解组合优化问题
使用这种技术的算法,不是递归调用自身,但是问题的基础解通常是用递归函数的形式来说明的。

最长公共子序列问题

问题描述

有两个长度分别为n和m的字符串A和B,确定A和B中最长公共子序列的长度。这里,A=a1a2⋯anA = a_1a_2\cdots a_nA=a1a2an的子序列是一个形式为ai1ai2...aika_{i_1}a_{i_2}...a_{i_k}ai1ai2...aik的字符串,其中每个iji_jij都在1和n之间,并且1≤i1&lt;i1&lt;⋯&lt;ik≤n1\leq i_1&lt;i_1&lt;\cdots &lt;i_k\leq n1i1<i1<<ikn。假设A=zxyxyz,B=xyyzx,很很显最长公共子序列为xyyz。

解决思路

为了使用动态规划技术,首先寻找一个递推公式,令A=a1a2⋯an,B=b1b2⋯bmA = a_1a_2\cdots a_n, B = b_1b_2\cdots b_mA=a1a2an,B=b1b2bm,令L[i.,j]L[i.,j]L[i.,j]表示a1a2⋯aia_1a_2\cdots a_ia1a2aib1b2⋯bjb_1b_2\cdots b_jb1b2bj的最长公共子序列的长度。注意,iiijjj可能是0,此时 L=0L = 0L=0
下面给出递推式
L[i,j]={0if i=0 or j=0L[i−1,j−1]+1if i&gt;0,j&gt;0 and ai=bjmax{L[i,j−1],L[i−1,j]}if i&gt;0,j&gt;0 and ai≠bj L[i, j] = \begin{cases} 0 &amp; \text {if $i= 0$ or $ j=0$} \\ L[i-1,j-1]+1&amp;\text{if $ i&gt;0,j&gt;0$ and $a_i = b_j$}\\ max\lbrace L[i,j-1],L[i-1,j]\rbrace &amp; \text{if $i&gt;0,j&gt;0$ and $a_i\neq b_j$} \end{cases} L[i,j]=0L[i1,j1]+1max{L[i,j1],L[i1,j]}if i=0 or j=0if i>0,j>0 and ai=bjif i>0,j>0 and ai̸=bj

算法

对于每一对i和j的值,0≤i≤n,0≤j≤m0\leq i\leq n,0\leq j\leq m0in,0jm,用一个(n+1)∗(m+1)(n+1)*(m+1)(n+1)(m+1)大小的表来计算存储L[i,j]L[i,j]L[i,j]的值

\\算法LCS
\\输入:字符串A和B,长度分别为n和m
\\输出:A和B最长公共子序列的长度
main(){
	for i = 0:n
		L[i,0] = 0
	for j = 0:m
		L[0,j] = 0
	for i = 1:n
		for j = 1:m
			if a[i] = b[j]
				L[i,j] = L[i-1,j-1] + 1
			else
				L[i,j] = max{L[i,j-1],L[i-1,j]}
	return L[n, m]
}

0/1背包问题

问题描述

U={u1,u2,⋯&ThinSpace;,un}U=\lbrace u_1,u_2, \cdots ,u_n\rbraceU={u1,u2,,un}是一个准备放入容量为CCC的背包中的nnn项物品的集合。对于1≤j≤n1\leq j \leq n1jn,令sjs_jsjvjv_jvj分别为第jjj项物品的体积和价值,这里,C,sj,vjC,s_j,v_jC,sj,vjjjj都是正整数。我们要解决的问题四用U中的一些物品来装满背包,这些物品的总体积不超过CCC,然而要使他们的总价值最大。

解决思路

V[i,j]V[i,j]V[i,j]表示从前iii{u1,u2,⋯&ThinSpace;,ui}\lbrace u_1,u_2, \cdots ,u_i\rbrace{u1,u2,,ui}中取出来的装入体积为jjj的背包的物品的最大价值。这里,0≤i≤n0\leq i\leq n0in0≤j≤C0\leq j \leq C0jC。找出递推式
V[i,j]={0if i=0 or j=0V[i−1,j]if j&lt;simax{V[i−1,j],V[i−1,j−si]+vi}if i&gt;0,j≥si V[i, j] = \begin{cases} 0 &amp; \text {if $i= 0$ or $ j=0$} \\ V[i-1,j]&amp;\text{if $ j&lt;s_i$}\\ max\lbrace V[i-1,j],V[i-1,j-s_i]+v_i\rbrace &amp; \text{if $i&gt;0,j\geq s_i$} \end{cases} V[i,j]=0V[i1,j]max{V[i1,j],V[i1,jsi]+vi}if i=0 or j=0if j<siif i>0,jsi

算法

\\算法KNAPSACK
\\输入:物品集合U={u1, u2, ..., un},体积分别为s1, s2,...,sn,价值分别为v1, v2, v3,..., vn,容量为C的背包
\\输出:满足条件的最大总价值
main(){
	for i = 0:n
		V[i,0] = 0
	for j = 0:C
		V[0,j] = 0
	for i = 1:n
		for j = 1:C
			if s[i] <= j
				V[i,j] = max{v[i,j],L[i-1,j-si]+vi}
			else
				V[i,j] = V[i-1,j]
	return V[n, C]
}

总结

很明显,无论是公共子序列还是背包问题,最关键的就是能找到合适的递推公式。说白了,还是要数学直觉强,加上见多识广。数学才是王道啊!tt,你说是不是。

卡片计分——附加题

问题描述

小a和小b玩一个游戏,有n张卡牌,每张上面有两个正整数x,y。取一张牌时,个人积分增加x,团队积分增加y。求小a,小b各取若干张牌,使得他们的个人积分相等,且团队积分最大。

输入描述

第一行n,接下来n行,每行两个正整数x,y

输出描述

一个整数,表示团队积分的最大值

示例

输入:
4
3 1
2 2
1 4
1 4
输出:
10

数据范围

0 < n < 100
0 < x < 1000
0 < y < 1e6

算法

很难想象这道题竟然也能用动态规划,数学果然令人畏惧!我当然也是看了网上的才懂。以L[i,j]L[i,j]L[i,j]表示前iii张牌中,个人积分相差jjj时,获得的最大团队积分,0≤i≤n,0≤j≤xmax0\leq i\leq n,0\leq j\leq x_{max}0in,0jxmaxxxx数组表示个人积分,yyy数组表示团队积分。这里给出递推式,注意:个人认为网上的程序(就是t1,t2t1,t2t1,t2条件)有一点问题,因为要考虑$L[i-1][j-x[i-1]] 是否大于0,即如果等于0,那么就无法组成前是否大于0,即如果等于0,那么就无法组成前00i张牌中,个人积分相差张牌中,个人积分相差j-x[i-1]时的情况,此时时的情况,此时t1只能等于0;只能等于0;0j < x[i-1]也要考虑;还有就是积分相差为0的情况,因为此情况必然存在,即两边都不拿牌,也就是也要考虑;还有就是积分相差为0的情况,因为此情况必然存在,即两边都不拿牌,也就是0j == x[i-1]的特殊情况。
L[i,j]={0if i=0max{L[i−1,j],t1,t2}if i&gt;0 L[i, j] = \begin{cases} 0 &amp; \text {if $i= 0$} \\ max\lbrace L[i-1,j],t1,t2\rbrace &amp; \text{if $i&gt;0$} \end{cases} L[i,j]={0max{L[i1,j],t1,t2}if i=0if i>0
t1={0othersL[i−1][diff]+y[i−1] if L[i−1][diff]&gt;0 or diff==0)diff=abs(j−x[i−1]) t1 = \begin{cases} 0 &amp; \text {others} \\ L[i-1][diff] + y[i-1] &amp;\text{ if $L[i-1][diff] &gt; 0$ or $diff== 0)$} \end{cases} diff = abs(j - x[i-1]) t1={0L[i1][diff]+y[i1]others if L[i1][diff]>0 or diff==0)diff=abs(jx[i1])
t2={0othersL[i−1][j+x[i−1]]+y[i−1]if j+x[i−1]≤xmax and L[i−1][j+x[i−1]]&gt;0 t2 = \begin{cases} 0 &amp; \text {others} \\ L[i-1][j+x[i-1]] + y[i-1] &amp;\text{if $j+x[i-1] \leq x_{max}$ and $L[i-1][j+x[i-1]] &gt; 0$} \end{cases} t2={0L[i1][j+x[i1]]+y[i1]othersif j+x[i1]xmax and L[i1][j+x[i1]]>0

程序

//c++ 源码
#include<iostream> 
#include<vector>
using namespace std;
/*
4
3 1
2 2
1 4
1 4
如果使用网上的一些程序,第二个样例会失败
4
3 1
4 2
5 3
10 4
*/
int max3(int x, int y, int z){
	if(x < y)
		x = y;
	if(x < z)
		x = z;
	return x;
}
int main(){
	int n;
	cin >> n;
	int *x = new int[n];
	int *y = new int[n];
	int max = 0;
	vector<vector<int> > help;
	for(int i = 0; i < n; ++ i){
		cin >> x[i] >> y[i];
		if(x[i] > max)
			max = x[i];
	}
	for(int i = 0; i < n + 1; ++ i){
		vector<int> temp(max + 1, 0);
		help.push_back(temp);
	}
	for(int i = 1; i < n + 1; ++ i){
		printf("%d ",x[i-1]);
		for(int j = 0; j < max + 1; ++ j){
			int temp1 = 0, temp2 = 0;
			int diff = j - x[i-1];
			if (diff < 0) 
				diff = 0 - diff;
			if((help[i-1][diff] > 0 || diff == 0)
			//注意,就是条件这里不太一样
				temp1 = help[i-1][diff] + y[i-1];
			if( j + x[i-1] <= max && help[i-1][j+x[i-1]] > 0)
				temp2 = help[i-1][j+x[i-1]] + y[i-1];
			help[i][j] = max3(help[i - 1][j], temp1, temp2);
			printf("%d  ",help[i][j]);	
		}
		printf("\n");
	}
	cout << help[n][0] << endl;
	delete []x;
	delete []y;
	
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值