构建有向无环图(DAG)模型解决矩形嵌套问题 以(nyoj16为例)

 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;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值