dp题目思路理解 (嵌套镶嵌问题DAG)

本文深入探讨了一类经典的动态规划(DP)问题——嵌套镶嵌问题,通过实例分析如何利用有向无环图(DAG)来辅助理解和求解。文章详细阐述了问题背景,关键思路,并提供了详细的解题步骤,帮助读者掌握此类问题的解决技巧。
摘要由CSDN通过智能技术生成
描述
有n个矩形,每个矩形可以用a,b来描述,表示长和宽。矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a<c,b<d或者b<c,a<d(相当于旋转X90度)。例如(1,5)可以嵌套在(6,2)内,但不能嵌套在(3,4)中。你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一个矩形都可以嵌套在下一个矩形内。
输入
第一行是一个正正数N(0<N<10),表示测试数据组数,
每组测试数据的第一行是一个正正数n,表示该组测试数据中含有矩形的个数(n<=1000)
随后的n行,每行有两个数a,b(0<a,b<100),表示矩形的长和宽
输出
每组测试数据都输出一个数,表示最多符合条件的矩形数目,每组输出占一行
样例输入
1
10
1 2
2 4
5 8
6 10
7 9
3 1
5 8
12 10
9 7
2 2
样例输出


5

若x能镶嵌于y时,x与y就有一条有向边,并且是不可能存在环的;

dp最重要的是状态和状态转移方程,可以从以下考虑:

1)状态:如果dp(i)表示最多能镶嵌几层,那么我们就要求dp(0);

2)状态如何转移或者说有哪些决策,我们在这个状态下能做什么:显而易见,我们只能顺着有向边走,即找个比当前矩形大的走,dp(i)-->dp(j) |(i,j)∈E;

3)对当前状态来说,那么多决策选哪个好:当然是最大的好,求的就是最大值,每一步都求最大,那么总的肯定最大,有点像贪心;

综上   dp(i)=max{dp(j)+1|(i,j)∈E};   注:dp(i)的值用个数组保存;


我觉得对于dp还有一种思路理解:

若将所有有向边的画出来,就是一棵树;

我们要求得就是根的最大树高;

同时对于每一层节点来说他们的高度取决于他们最高的子树的高度+1;

所以将dp(i)想象成第i个节点的最大高度就好;

那么状态转移方程也可以那么想:dp(i)=max{dp(j)+1|(i,j)∈E}; i节点的树高就是他儿子中最高的那个+1;


dp有两种方法

1)递推方法(一般两个for循环,外面那个for是1~状态数,里面那个for是1~决策总数) 

   从终点出发回到起点,也即是从树的单子叶节点出发,逆着层序遍历回到根,那么对于每一节点的高就十分简单就推出来,好比数塔问题就是这样;

2)记忆化搜索(一般递归函数)

    从起点(根)出发,即一个状态出发,取最有利的路走到下一状态;


dp简单来说就是每种路径都走一遍,得出最有利的路径,便走这条路;


至于路径打印就逆着推就好了;根据d[i],因为走一步就+1,所以比d[i]小1的肯定就是i的下一步;

下面是该题目的代码:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int d[500],G[50][50],n;
struct Rectangle{
	int x;
	int y;
	Rectangle(int x=0,int y=0):x(x),y(y) {}
	
};
ostream& operator << (ostream& out,const Rectangle A)
{
	out << "(" << A.x <<','<<A.y<<')';
}

int dp(int i)
{
	int& ans = d[i];
	if(ans > 0) return ans;  //表示该点值已经计算过了,直接返回 
	ans = 1;
	for(int j=0;j<n;j++)
      if(G[i][j]) ans=max(ans,dp(j)+1); 
	return ans;
}
void print_ans(int i)
{
	cout<<i<<' ';
	for(int j=0;j<n;j++)
	if(G[i][j]&&d[i]==d[j]+1)
	{
		print_ans(j);
		break;
	}		
}
void print(int *A,int m)  //打印最小字典序的答案 
{
	for(int i=0;i<m;i++)
	cout<<A[i]<<' ';
	putchar('\n');
}
void print_allans(int *A,int i,int cur)  //打印所有答案 
{
	A[cur]=i;
	if(d[i]==1){
		print(A,cur+1);
	}
	else for(int j=0;j<n;j++)
	{
		if(G[i][j]&&d[i]==d[j]+1)
		{
			print_allans(A,j,cur+1);
		}
	}
}
bool operator < (Rectangle A,Rectangle B) 
{
	if((A.x<B.x&&A.y<B.y)||(A.y<B.x&&A.x<B.y)) return true;
	return false;
}
int main()
{
	int k;
	Rectangle R[500];
	cin>>k;
	while(k--)
	{
		memset(d,0,sizeof(d));
		memset(G,0,sizeof(G));
		cin>>n;
		int m=n;
		int i=0;
		while(m--)
		{
			cin>>R[i].x>>R[i].y;
			i++;
		}
		for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			if(i!=j&&R[i]<R[j]) G[i][j]=1;
			
		sort(R,R+n); 
		
		cout<<dp(0);
		
//		int a[100];
//		print_allans(a,0,0);
		
//		print_ans(0);

		if(k) cout<<endl;
	}
} 


接下来系统总结一下:

状态有两种实现方法:1)设d(i)为从i点出发的最长路,则d(i)=max{dp(j)+1|(i,j)∈E};     即 从前往后,起点出发;

             2)设d(i)为从i点结束的最长路,则d(i)=max{dp(j)+1|(j,i)∈E};     即 从后往前,终点出发;

实现方法: 1)记忆化搜索:上面的状态1)法,正着递推,也即是填表法:对于每一个状态i,找到f(i)依赖的所有状态; 如:f(i) 的值要靠f(Vj)算          出来,其中Vj为i的相连点,于是去算f(Vj);

       2)递推:上面的状态2)法,反着递推,也称为刷表法:是从Vj到i ,是因为每个d[Vj]会影响到d[i]的数值,所以“对于每个状态i,更          新f(i)所影响到的状态”,类似Dijkstra 更新i点周围所有相邻点Vj的d[Vj]值;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值