第九章 动态规划相关知识点总结

一、动态规划

动态规划的核心是 状态 和 状态转移方程。

解决动态规划的方法一般有两种 

1、递推计算

递推计算的关键是边界和计算顺序

2、记忆化搜索

记忆化搜索不用事先确定计算顺序,所谓的记忆化搜索,就是给每个状态设定一个标志,当这个状态已经被计算过,通过标志判断不再重复计算。

3、DAG上的动态规划

一般的问题可以转化为有向无环图的动态规划,一般分为不知道起点和终点的最长路,已知起点和终点的最长路和最短路。

(1)在未知起点的最长路中,我们可以写出状态转移方程

d(i)=max{d[j]+1 | (i,j)∈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[i][j]) ans = max(ans,dp[j]+1);
	return ans;
}
如果有多解,又要保证字典序最小,递归输出。那么:

void print_ans(int i)
{
	printf("%d",i);
	for (int j=1;j<=n;j++) if(G[i][j]&&d[i]==d[j]+1)
	{
		printf_ans(j);
		break;
	}
}

(2)固定终点的最长路和最短路(以硬币问题为例)

需要考虑终点不能到达的情况。

使用一个极小的数(特殊值)来表示终点不能到达代码:

int dp(int S)//S为还剩下的价值 
{
	int& ans =d[S]; //硬币数目 
	if(ans!=-1) return ans;
	ans = -(1<<30);
	for (int i=1;j<=n;i++) if(S>=V[i]) ans = max(ans,dp(S-V[i])+1);
	return ans;
	
}

使用vis[]来表示是否已经访问,代码:


int dp(int S)//S为还剩下的价值 
{
	if(vis[S]) return d[S];
	vis[S]=1;
	int& ans =d[S]; //硬币数目 
	ans = -(1<<30);
	for (int i=1;j<=n;i++) if(S>=V[i]) ans = max(ans,dp(S-V[i])+1);
	return ans;
	
}


如果状态复杂,可以使用map来记录状态值,通过if(d.count(S))可以来判断状态是否计算过。


如果既要求最短路,又要求最长路,使用记忆化搜索需要需要写两个,这时可以采用递推的方法。

代码:

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])
		{
			maxv[i]= max(maxv[i],maxv[i-V[j]]+1);
			minv[i]= min(minv[i],minv[i-V[j]]+1);
		}
printf("%d %d\n",minv[S],maxv[S]);

//print ans
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;
		}
}


实际上,无论我们使用递推计算还是记忆化搜索进行计算,计算的顺序都是从小的状态到大的状态,而大的状态可以根据小的状态的值进行求解。

传统的递推法可以表示为”对于每个状态i,计算f(i)“,这需要对于每个状态i,找到计算f(i)以来的所有状态,而另外一种方法是"对于每个状态i,更新f(i)所影响的状态",称为“刷表法”。

0-1背包问题

for(int i=n;i>=1;i++)//n种物品
	for(int j=0;j<=C;j++)//j表示的为背包的剩余重量
	{
		d[i][j] = (i==n?0:d[i+1][j]);
		if(j>=V[i]) //表示了背包剩余重量的所有可能,对这个进行了枚举 
			d[i][j]=max(d[i][j],d[i+1][j-V[i]]+W[i]);
			//V[i]为第i个物品的体积,W[i]为第i个物品的重量 
			//d[i][j]表示不选物品i, d[i+1][j-V[i]]+W[i]表示现在了物品i。 
	} 

采用滚动数组求解

	
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)
{
	scanf("%d%d",&V,&W);
	for (int j = C;j>=0;j--)
	if(j>=V) f[j]=max(f[j],f[j-V]+W);
}

经典动态规划模型

1、最长上升子序列问题

2、最长公共子序列问题

3、最优矩阵链乘

4、最优三角剖分

树上的动态规划

树上的动态规划,总体而言,就是计算顺序从叶子节点到根,那么需要进行DFS到叶子节点计算状态后返回。适合递归操作。

1、树的最大独立集

2、树的重心(质心)

3、树的最长路径(最远点对)

复杂状态的动态规划

1、最优配对问题

最优配对问题的状态定义为d(i,S)表示在前i个点,位于集合S的元素两两配对的最小距离和。状态转移方程为:

d(i,S)=min{ |PiPj| + d(i-1,S-{i}-{j)|  j∈S}

首先通过子集二进制方式表示下表,进行枚举子集,代码如下:

//枚举i 
for(int i = 0;i<n;i++)
	//枚举子集 
	for(int S=0;S<(1<<n);S++){
		d[i][S]=INF;
		for(int j=0;j<i;j++) if(S&(1<<j))
			d[i][S]=min(d[i][S],dist(i,j)+d[i-1][S^(1<<i)^(1<<j)]);
}


2、TSP问题

TSP问题是想寻找一条道路,从起点出发,最终回到起点,最终道路的总长度最短。可以设起点和终点都为0.

可以设置状态为d(i,S)为当前在城市i, 还需访问集合S中的城市各一次后回到城市0的最短长度,则

d(i,S) = min (d(j,S-{j})+dist(i,j) | j∈S)

边界为d(i,{})= dist(0,i);

3、图的色数

图的色数问题是在一个无向图中,把图中的节点染成尽量少的颜色,使得相邻结点颜色不同。

d(S) 表示把结点S染色,所需要的颜色数的最小值,则

d(S)=d(S-S‘)+1 其中S‘是S的子集,并且内部没有边(即不存在S‘内的两个结点u和v使得u和v相邻),换句话说S‘是一个“可以染成同一种颜色”的结点集。

代码如下:

d[0]=0;
for(int S=1;S< (1<<n);S++)
{
	d[S]=INF;
	for(int S0=S;S0;S0 = (S0-1)&S)
		if(no_edge_inside[S0])
			d[S]=min(d[S],d[S-S0]+1);
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值