DAG(Directed Acyclic Graph):在图论中,如果一个有向图无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG图)。有向无环图上的动态规划是学习动态规划的基础。很多问题都可以转化为DAG上的最长路和最短路或计数问题。
本文以nyoj16 为例:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=16
分析
矩形之间的“可嵌套”关系是一个典型的二元关系,二元关系可以用图来建模。如果矩形X可以嵌套在Y里面,那么从X到Y就有一条有向边。这个图是无环的,因为一个矩形无法嵌套在自己内部。换句话说,它是一个DAG。这样,所要求的便是DAG上的最长路径。
对于DAG最长(短)路,有两种“对称”的状态定义方式:
(1)自顶向下:设d(i)为从i出发的最长路,则d(i)=max{d(j)+1|(i,j)∈E}E为边集
(2)自底向上:设d(i)为以i结束的最长路,则d(i)=max{d(j)+1|(j,i)∈E}
注意:虽然最长上升子序列(LIS)可以看做DAG模型,(对于LIS:如果某一子序列中的元素i大于前面的某一元素j,那么就说从j到i有一条边,显然整个关系图是无向的)但最长上升子序列和矩形嵌套问题又有不同点把。LIS问题中元素位置是固定的,可以直接用动态规划求解。在矩形嵌套问题中把每个矩形用数字表示也可以转化为类似LIS问题,但是里的的元素可以移动。不过有一种巧妙的方式是把矩形表示的元素按长边先排序,这时求解矩形嵌套对应的最长子序列就无需移动位置了,可以用动态规划求。
方法一:将问题用DAG模型建模,采用记忆化搜索自顶向下求最长路径。
#include<iostream>
#include<algorithm>
#include<list>
using namespace std;
#define T 1001
int x[T], y[T];//保存每个矩形的长宽
int dp[T],n;//记录从某结点出发可经过的最大长度 n表示矩形的数量
list<int>edge[T];//记录图中的边
inline bool isNest(int cur, int pre)//判断cur是否嵌套在pre中
{
return x[cur] < x[pre] && y[cur] < y[pre] || x[cur] < y[pre] && y[cur] < x[pre];
}
int dfs(int x)//自顶向下递归搜索每个结点
{
if (dp[x] == 0)//剪枝
{
dp[x] = 1;
for (list<int>::iterator it = edge[x].begin(); it != edge[x].end(); it++)
dp[x] = max(dp[x], dfs(*it) + 1);
}
return dp[x];
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
//freopen("debug.txt", "r", stdin);
int i, j,maxlen;
short N;
cin >> N;
while (N--)
{
cin >> n;//下面初始化使用变量
fill_n(dp, n, 0);
maxlen = 0;
for (i = 0; i < n; i++)//输入并构建图模型
{
edge[i].clear();
cin >> x[i] >> y[i];
for (j = 0; j < i; j++)
{
if (isNest(i, j))edge[j].push_back(i);
else if (isNest(j, i))edge[i].push_back(j);
}
}
for (i = 0; i < n; i++)
maxlen = max(maxlen, dfs(i));
cout << maxlen << endl;
}
return 0;
}
方法二:
如果只需要求得最多可以嵌套多少个矩形,而不要求输出序列,定义一个结构体,内含有变量a,b,输入时保证a>b(a为长,b为宽)对a进行排序,最后采用自底向上的方法求(a,b)的最长上升子序列。
#include<iostream>
#include<algorithm>//转化为最大上升子序列
using namespace std;
#define T 1000
struct node
{
int l, w;
bool operator >(node&b)
{
return l > b.l&&w > b.w;
}
};
bool cmp(const node&a, const node&b)
{
return a.l < b.l;
}
node rec[T];
int dp[T + 1];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
//freopen("debug.txt", "r",stdin);
int N,i,n,j,maxlen;
cin >> N;
while (N--)
{
cin >> n;
for (i = 0; i < n; i++)
{
cin >> rec[i].l >> rec[i].w;
if (rec[i].w > rec[i].l)swap(rec[i].l, rec[i].w);
}
sort(rec, rec + n, cmp);//将问题转化为最长递增子序列
dp[0] = 1;
for (i = 1; i < n; i++)
{
maxlen = 1;
for (j = i - 1; j >= 0; j--)
if (rec[i] > rec[j] && dp[j] + 1 > maxlen)maxlen = dp[j] + 1;
dp[i] = maxlen;
}
cout << *max_element(dp,dp+n) << endl;//求数组中最大值
}
return 0;
}