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

来源:互联网 发布:域名可以干什么 编辑:程序博客网 时间:2024/05/29 18:25
描述
有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),表示矩形的长和宽
输出
每组测试数据都输出一个数,表示最多符合条件的矩形数目,每组输出占一行
样例输入
1101 22 45 86 107 93 15 812 109 72 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