算法笔记上机指南———动规

最大连续子序列

数据num(N)
令d(i)表示num(i)最为末尾的连续序列的最大和。
转移方程

d(i)=max(d(i-1)+num(i),num(i))

有一点需要注意,递归数组的最后未必是以num(N)最大最大

核心代码
	dp[0] = num[0];
	for (int i = 1; i <N; i++) {
		if (dp[i - 1] + num[i] > num[i]) {
			dp[i] = dp[i - 1] + num[i];
			//indx[i] = indx[i - 1];
		}
		else {
			dp[i] = num[i]; //indx[i] = i;
		}
	}
	int flag=0;
	for(int i=1;i<=n;i++)
		if(d[flag]<d[i])   flag=i;

最长不下降序列(LIS)

在一连串数字中找出最长的非递减串,不需要连续
令 d(i)表示以num(i)结尾的最长非下降序列

d(i)=max(i,dp(j)+1)(j=1,2,3.....i-1&&num(j)<num(i))

同样最后的数据也未必是最大的
	memset(dp, 0, sizeof(dp));
	int ans = -1;
	for (int i = 0; i < num; i++) {
		for (int j = 0; j < i; j++) {
			if (b[i] >= b[j] && dp[i] > dp[j] + 1)
				dp[i] = dp[j] + 1;
		}
		ans = max(dp[i], ans);
	}

最长公共子序列

(1)只能一一对应,一个字符匹配过后不能在匹配第二次

d(i,j)=max( d(i-1,j),d(i,j-1),d(i-1,j-1)+(s1[i]==s2[j]) )

(2)同一个字符可以连续匹配多次,注意是连续,不能间隔

d(i,j)=max( d(i-1,j),d(i,j-1))+(s1[i]==s2[j])

for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++){
		d2[i][j] = max(max(d2[i - 1][j], d2[i][j - 1]),d[i-1][j-1]+(a[i] == b[j]))
		d2[i][j] = max(d2[i - 1][j], d2[i][j - 1]) + (a[i] == b[j]);
		}

最长回文子串

即给定一个字符串s,判断所谓s中最长的回文子序列
这里容易犯一个错误:把这个问题转化为最长公共子序列问题,将S逆序T,然后匹配出一个(LCS),但是如果匹配的字符串序列不是连续的,结果一定出错,就算是连续的也不可以,因为即使是回文子串也未必能吻合
最简单的 S=" abcdba", T=“abdcab”
我们对长度进行匹配

for (int i = 0; i < len; i++) {
		d[i][i] = 1;   //初始化为1
		if (i + 1 < len&&s[i] == s[i + 1])  d[i][i + 1]=1,ans=2;  //提前判断长度为2的。
	}
	for (int L = 3; L <= len; L++) {
		for (int i = 0; i + L - 1 < len; i++) {
			int j = i + L - 1;
			if (d[i + 1][j - 1] && s[i] == s[j]) d[i][j] = 1, ans = L;
		}
	}

DAG最短路,最长路

看过了好多文章,截止到现在最难理解的就是构图是如何构图的,使用那样的转移方程为什么不会出现闭环,出现死循环,还有最重要的一点就是关于输出时的字典序问题,如何最小,如何最大,都是问题啊啊啊啊啊,难理解,先记在这里,将来还是会忘的?
解惑: 我们首先来看两个例子 参自刘汝佳《算法入门经典》

1.嵌套矩形问题。有n个矩形,每个矩形可以用两个整数a、b描述,表示它的长和宽。矩 形X(a,b)可以嵌套在矩形Y(c, d)中,当且仅当a<c,b<d,或者b<c,a<d(相当于把矩 形X旋转90°)。例如,(1, 5)可以嵌套在(6, 2)内,但不能嵌套在(3, 4)内。你的任务是选出尽 量多的矩形排成一行,使得除了最后一个之外,每一个矩形都可以嵌套在下一个矩形内。如 果有多解,矩形编号的字典序应尽量小。

2.硬币问题。有n种硬币,面值分别为V1, V2, …, Vn,每种都有无限多。给定非负整数S, 可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值和最大值。 1≤n≤100,0≤S≤10000,1≤Vi≤S。

(1)问:如何构图?
答:我们把每一个矩形视作一个顶点,对于A,B两个矩形,如果B可以完全嵌套进A,即B完全可以把A包进去,就让建立一个从A至B点的边(这里我们设为小的指向大的矩形,当然也可以大的矩形指向小的矩形),权值为1;
(2)为什么不会出现闭环?
答:何为环?图中存在一点,或者几点从自身出发,总有一条路径可以使它再次回到自身上来。既然如此,我们不妨设从X出发通过一条路径回到Y再到X当中来,那么Y小于X,但从我们的定义上来看,X所指向的每一个顶点,必然会比X大,矛盾!!那有没有可能使自身成环呢,当然有可能,无可能,题目中所给是完全嵌套,不存在相等的情况下,因此,我们所构建的图中一定不存在环。那也就不存在死循环了。


这里注意一下,我们所构建的图的起点并不唯一,终点也不唯一。
贴一下刘大神的代码

入口  
**转移方程 :d(i)=max(d(j)+1,d(i))  (i,j)属于E**

   for(int i=1;i<=n;i++)
		solve=dp(i);
int dp(int i)
 {  int& ans = d[i];   
  	if(ans > 0) return ans;   0表示没有被访问,因为一个矩形嵌套算上自己
 	ans = 1;  
    for(int j = 1; j <= n; j++)      
    if(G[i][j])	 ans = max(ans, dp(j)1); 
    return ans; 
   }
   void print_ans(int i) {2  
  	 printf("%d ", i);
  	 for(int j = 1; j <= n; j++) 
 	 if(G[i][j] && d[i] == d[j]1){  
   	 print_ans(j);    break; 
     } 
   }

   
也可以逆向   
 d(i)=max(d(j)+1,d(i))  (j,i)属于E
int dp(int i)
 {  int& ans = d[i]; 
  	if(ans > 0) return ans; 
 	ans = 1;  
    for(int j = 1; j <= n; j++)      
    if(G[j][i])	 ans = max(ans, dp(j)1); 
    return ans; 
   }
//

(3)如何保证确定的路径字典序最小呢?
字典序最小是一种最常见的求唯一解的方法
最笨的方法就是把每一个路径都记录下来,然后比较。
还有一种更好的方法就是直接判断d(i),对于相同的d(i)我们选取最小的i即可,如果是这样的话就必须选择第一种构图方法,我只是再代码里附上另一种方法。
(4)如何打印路径
按照求最长路的方法打印即可。
拓扑方法有空再写

再看第二题:
(1)问:如何构图?
我们选取0–S作为顶点,硬币的额度为边,例如我们有1元,2元硬币,这里的起点和终点是唯一的,起点是S,终点是0;
那么0-1,1-2,2-3,3-4…(S-1)-S存在权值为1的边
0-2,1-3,2-4.。。。。(S-2)–S存在权值为2的边,以此类推。
边权同样是1;
(2)为什么不会出现闭环?
我们同样从X出发通过一条路径回到Y再到X当中来,那么Y一定大于X,但从我们的定义上来看,X所指向的每一个顶点,必然会比X小,矛盾!!
因此不存在环。

最长路算法
初始化不要为0,因为0也可使是结果,这与嵌套矩形不同。
int dp(int S){ 
 int& ans = d[S]; 
  if(ans !=1) return ans; 
   ans =(1<<30); 
    for(int i = 1; i <= n; i++) 
    if(S >= V[i]) 
    ans = max(ans, dp(S-V[i])1); 
     return ans; 
 }
后一种使用辅助数组判断是否被访问,前一种是d[i]=-1表示没有被访问
int dp(int S){  
  if(vis[S]) return d[S]; 
  vis[S] = 1;  
  int& ans = d[S];
  ans =(1<<30);  
  for(int i = 1; i <= n; i++) 
  if(S >= V[i]) 
  	ans = max(ans, dp(S-V[i])1);  return ans; }

也可以使用递推,这个不需要先拓扑排序,因为本身就有一定的顺序

minv[0] = maxv[0] = 0;
 for(int i = 1; i <= S; i++){
   minv[i] = INF; maxv[i] = -INF;   }
   
  for(int i = 1; i <= S; i++)  
 	 for(int j = 1; j <= n; j++)    
 		 if(i >= V[j]){      
 			 minv[i] = min(minv[i], minv[i-V[j]] + 1);      
			 maxv[i] = max(maxv[i], maxv[i-V[j]] + 1);    
  } 
  其实也可以看作是完全背包一维数组的写法,后面会提到
  printf("%d %d\n", minv[S], maxv[S])

字典序输出,矩形嵌套。

void print_ans(int* d, int S){  
for(int i = 1; i <= n; i++)    
	if(S>=V[i] && d[S]==d[S-V[i]]+1){      
	printf("%d ", i);      
	print_ans(d, S-V[i]);      
	break;    
	}
}

还有一种方法就是刘大神书中写的,这里先贴上,有空在看
很多用户喜欢另外一种打印路径的方法:递推时直接用min_coin[S]记录满足min[S] ==>min[S-V[i]]+1的最小的 i,则打印路径时可以省去print_ans函数中的循环,并可以方便 地把递归改成迭代(原来的也可以改成迭代,但不那么自然)。具体来说,需要把递推过程 改成以下形式:

for(int i = 1; i <= S; i++)  
	for(int j = 1; j <= n; j++)    
		if(i >= V[j]){      
			if(min[i] > min[i-V[j]] + 1){        
				min[i] = min[i-V[j]] + 1;        
				min_coin[i] = j;      }      
			if(max[i] < max[i-V[j]] + 1){        
				max[i] = max[i-V[j]] + 1;        
				max_coin[i] = j;      
	}    
}

注意,判断中用的是“>”和“<”,而不是“>=”和“<=”,原因在于“字典序最小解”要求 当min/max值相同时取最小的i值。反过来,如果j是从大到小枚举的,就需要把“>”和“<”改 成“>=”和“<=”才能求出字典序最小解。
在求出min_coin和max_coin之后,只需调用print_ans(min_coin, S)和print_ans(max_coin, S) 即可。

用时间换空间,有空在看
void print_ans(int* d, int S){  
while(S){    
	printf("%d ", d[S]);   
 	S -= V[d[S]];  
 	} 
 }

接下来就是重头戏

背包问题

0-1背包

给定一批物体N,每个物体各有重量weight[i]和价值value[i],求出在满足背包重量的情况下,每个物品只能装一次,那么最大价值是多少

d(i,j)=max(d(i-1,j),d(i-1,j-weight[i])+value[i]);

二维数组
memset(d,0,szieof(d))
for(int i=1;i<=n;i++)
		for (int j = v; j >= weight[i]; j--) {
			d[i][j]=max(d[i-1][j],d[i][j-weight[i]]+value[i]);
int i=n,j=v;
while(i>=0){
	if(d[i][j]==d[i-1][j-weight[i]]+value[i])    //装入i个物品{
		cout<<i<<' ';
		j-=weight[i]; 
	}
	i--;
}
优化  一维滚动数组
for(int i=1;i<=n;i++)
		for (int j = v; j >= weight[i]; j--) {
			if (d[j]<=d[j - weight[i]] + value[i]) {
				d[j] = d[j - weight[i]] + value[i];
				choose[i][j] = 1;   
			}
			else  choose[i][j] = 0;
		}
while (i > 0) {   //这里采用了辅助数组来输出
		if (choose[i][j] == 1) {
			cout << weight[i] << ' ';
			j -= weight[i];
		}
		i--;
	}

关于滚动数组的一些问题可以查看大佬

完全背包

数量上无限个

d(i,j)=max(d(i-1,j),d(i,j-weight[i])+value[i]);

memset(d,0,sizeof(d));
for (int i = 1; i <= n; i++) {   //完全背包
		for (int j = weight[i], j <= v; j++)
			d[i][j] = max(d[i - 1][j], d[i][j - weight[i]] + value[i]);
	}
	一维数组
	for (int i = 1; i <= n; i++) {   //完全背包
		for (int j = weight[i], j <= v; j++)
			d[j] = max([j], d[j - weight[i]] + value[i]);
	}
	建议使用辅助数组进行输出方案,同上
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值