有n个矩形,每个矩形可以用a,b来描述,表示长和宽。矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a < c,b < d或者b < c,a < d(相当于旋转X90度)。例如(1,5)可以嵌套在(6,2)内,但不能嵌套在(3,4)中。你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一个矩形都可以嵌套在下一个矩形内。
1.将问题抽象为适合DP的方向:
矩形之间的“可嵌套”是一种典型的有向二元关系,并且由于矩形不能自己嵌套,所以可以根据矩形的嵌套关系建出一个DAG,所求就变成了求DAG中不固定起点的最长路。
2.状态转移方程:
状态定义:d(i)表示以节点i为起点的最长路径长度
int dp(int i){
int& ans = d[i];
if (ans > 0) return ans;
ans = 1;
_for(j,0,n)
if(G[i][j])
ans = max(ans, dp(j)+1);
return ans;
}
3.最后的输出问题:
NYOJ16原题只要求输出最优解的矩形个数ans即可,但书中还讲了
(1).输出字典序最小的最优解。(矩形排列)
(2).输出所有最优解,并按字典序排序。
方法仅仅是预先保留最优解的矩形个数,随后在d中找,找到以后按字典序往下找即可,详见代码。
1.NYOJ16
#include
#include
#include
#include
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int maxn = 1000+10;
struct juxing{
int a,b;
}A[maxn];
int n, G[maxn][maxn], d[maxn];
int dp(int i){
int& ans = d[i]; // 技巧
if (ans > 0) return ans;
ans = 1;
_for(j,0,n)
if(G[i][j])
ans = max(ans, dp(j)+1);
return ans;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
_for(i,0,n) scanf("%d%d",&A[i].a,&A[i].b);
memset(G,0,sizeof(G));
_for(i,0,n)
_for(j,0,n)
if (i != j)
if ((A[i].a < A[j].a && A[i].b < A[j].b) || (A[i].a < A[j].b && A[i].b < A[j].a))
G[i][j] = 1;
memset(d,0,sizeof(d));
int ans = 1;
_for(i,0,n) ans = max(ans, dp(i));
printf("%d\n",ans);
}
return 0;
}
2.输出字典序最小的最优解。
#include
#include
#include
#include
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int maxn = 1000+10;
struct juxing{
int a,b;
}A[maxn];
int n, G[maxn][maxn], d[maxn];
int dp(int i){
int& ans = d[i]; // 技巧
if (ans > 0) return ans;
ans = 1;
_for(j,0,n)
if(G[i][j])
ans = max(ans, dp(j)+1);
return ans;
}
void print_ans(int i){
printf("%d ",i+1);
_for(j,0,n) if (G[i][j] && d[i] == d[j]+1){
print_ans(j);
break;
}
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
_for(i,0,n) scanf("%d%d",&A[i].a,&A[i].b);
memset(G,0,sizeof(G));
_for(i,0,n)
_for(j,0,n)
if (i != j)
if ((A[i].a < A[j].a && A[i].b < A[j].b) || (A[i].a < A[j].b && A[i].b < A[j].a))
G[i][j] = 1;
memset(d,0,sizeof(d));
int ans = 1;
_for(i,0,n) ans = max(ans, dp(i));
_for(i,0,n)
if (ans == d[i]){
print_ans(i);
break;
}
printf("\n");
}
return 0;
}
3.按字典序顺序,输出所有最优解
#include
#include
#include
#include
#include
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int maxn = 1000+10;
struct juxing{
int a,b;
}A[maxn];
int n, G[maxn][maxn], d[maxn], ans;
vector<int> ansvec;
int dp(int i){
int& ans = d[i]; // 技巧
if (ans > 0) return ans;
ans = 1;
_for(j,0,n)
if(G[i][j])
ans = max(ans, dp(j)+1);
return ans;
}
void print_ans(int i, int dep){
ansvec.push_back(i);
if (dep == ans-1){
for (vector<int>::iterator iter = ansvec.begin(); iter != ansvec.end(); ++iter)
printf("%d ",*iter+1);
printf("\n");
}
_for(j,0,n) if (G[i][j] && d[i] == d[j]+1){
print_ans(j,dep+1);
}
ansvec.pop_back();
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
_for(i,0,n) scanf("%d%d",&A[i].a,&A[i].b);
memset(G,0,sizeof(G));
_for(i,0,n)
_for(j,0,n)
if (i != j)
if ((A[i].a < A[j].a && A[i].b < A[j].b) || (A[i].a < A[j].b && A[i].b < A[j].a))
G[i][j] = 1;
memset(d,0,sizeof(d));
ans = 1;
_for(i,0,n) ans = max(ans, dp(i));
ansvec.clear();
_for(i,0,n)
if (ans == d[i]){
print_ans(i,0);
}
}
return 0;
}
LRJ最后留了个问题,如果把状态定义成“d(i)表示以节点i为终点的最长路径长度”,也可以得出最优值,但很难打印出字典序最小的方案。
此刻暂时无法理解,感觉仅仅是状态的定义顺应解的输出,但也说不清楚。留给以后的自己看。