UVa 11132 例题8-4 传说中的车(Fabled Rooks)

本文详细解析了UVa-11134题目的解题思路,对比了两种不同的贪心算法实现方式。一种是基于优先队列的方法,成功通过了评测;另一种虽然逻辑上看似合理但在评测中失败。文章提供了完整的AC代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题链接: UVa-11134

题目大意:

  给出数字n代表马的个数和棋盘大小,接下给每n行,每个四个数字构成一个矩形区域表示该马的移动范围。写出一个程序判断,能不能满足如下条件:所有马都互相不能攻击(马的攻击范围是一行和一列),其次所有马都必须只能在自己的矩形范围内。如果满足按输入顺序输出每个马的位置。

解题思路:

 刚看到本题时候,觉得很像八皇后,使用回溯法求解。但是看了紫书上给的分析之后,发现思路不太对。正确的思路应该是:由于每个马的攻击范围只是他所在的那一行和那一列。所以行和列的选择之间没有联系,可以相互独立。这样一分析这道题更像是使用贪心的活动安排问题,也确实是这样。


 至于怎么贪,一开始我的思路是(代码二):根据每个马可以在的x,统计出来每个x上多少个马可以存在。然后每个马选位置x的时候就选可以存放马最少的那个x,选完之后把这个马的所能存在的位置的统计都减少1。实现了之后,样例测试数据都能通过,但是一直WA。不清楚为什么会出错。我也没有找到能证明错误的样例。所以不知道到底是不是错的,很无奈。有小伙伴可以证伪的话可以在评论里告诉我。


 然后我看了别的小伙伴的博客,换了一个思路(代码一)。这次贪的原则就是:每次先给r最小(右边界最靠左的)进行分配,分配可行而且最l最小的(l最靠左边的),贪的原理就是因为r越小的区间 其选择的灵活性就越低,所以要先对他进行分配。实现起来就是使用优先队列进行排序,每次取出最上边的进行分配给他最左边的值。细节参考代码。

代码:

代码一(AC):

//E8-4 Uva11134 
//思路:每次先给r最小(右边界最靠左的)进行分配,
//分配可行而且最l最小的(l最靠左边的),
//贪的原理就是因为r越小的区间 其选择的灵活性就越低,所以要先对他进行分配。
#include<iostream>
#include<cstring>
#include<queue>

using namespace std;

const int MAXN = 5000 + 10;

struct Node{
	int l, r, id;
	bool operator < (const Node &b) const {
		return (b.r < r || (r == b.r && b.l < l));
	}
};

Node maze_x[MAXN], maze_y[MAXN];
int rook_x[MAXN], rook_y[MAXN], mx[MAXN], my[MAXN];

int main()
{
	int n;
	while (cin >> n && n) {
		priority_queue<Node> pq_x;
		priority_queue<Node> pq_y;
		memset(mx, 0, sizeof(mx));
		memset(my, 0, sizeof(my));
		for (int i = 0; i < n; i++) {	//读入
			cin >> maze_x[i].l >> maze_y[i].l >> maze_x[i].r >> maze_y[i].r;
			maze_x[i].id = i; maze_y[i].id = i;
			pq_x.push(maze_x[i]); pq_y.push(maze_y[i]);	//放进优先队列中,l值越小越优先,l相等时r越小越优先
		}
		bool ok = true; int i, j;
		while (pq_x.size()) {
			Node rx = pq_x.top(); pq_x.pop();
			Node ry = pq_y.top(); pq_y.pop();
			for (i = rx.l; i <= rx.r; i++) { //按照优选队列中的顺序给每个rook分配x
				if (!mx[i]) {
					rook_x[rx.id] = i;
					mx[i] = 1; break;
				}
			}
			for (j = ry.l; j <= ry.r; j++) {
				if (!my[j]) {
					rook_y[ry.id] = j;
					my[j] = 1; break;
				}
			}
			if (i > rx.r || j > ry.r) {//不满足分配条件
				ok = false; break;
			}
		}
		if (ok)
			for (int i = 0; i < n; i++)
				cout << rook_x[i] << " " << rook_y[i] << endl;
		else
			cout << "IMPOSSIBLE \n";
	}
	return 0;
}

代码二(WA):

//E8-4 Uva11134 WA
//思路(正确性未知):由于题目中x和y之间互相不影响,所以x和y可以独立操作
//将棋盘上统计x轴每个位置上可以放多少个棋子,然后每次寻找位置时都找位置上能放棋子最少的
//看看所有棋子是否都能归位,y轴作同样处理 
#include<iostream>
#include<cstring>

using namespace std;

int find_min(int n,int *x, int l, int r);
void count(int n);
void stat(int i, int op);

const int MAXN = 5000 + 10;
const int INF = 0x7fffffff;

int arr[4][MAXN];
int maze_x[MAXN], maze_y[MAXN], rook_x[MAXN], rook_y[MAXN];

int main()
{
	int n;
	while (cin >> n && n) { 
		for (int i = 0; i < n; i++)
			for (int j = 0; j < 4; j++)
				cin >> arr[j][i];
		count(n);
		bool ok = true;
		for (int i = 0; i < n; i++) {
			int x = find_min(n,maze_x, arr[0][i], arr[2][i]);
			int y = find_min(n,maze_y, arr[1][i], arr[3][i]);
			if (!x || !y) {
				ok = false; break;
			}
			stat(i, -1);			//每次选择一个马之后要将对他的统计减去 
			rook_x[i] = x; maze_x[x] = INF;
			rook_y[i] = y; maze_y[y] = INF;
		}
		if (ok)
			for (int i = 0; i < n; i++)
				cout << rook_x[i] << " " << rook_y[i] << endl;
		else
			cout << "IMPOSSIBLE \n";
	}
	return 0;
}

int find_min(int n,int *x, int l, int r)//找到当前马的个数最少的位置 
{
	int min = x[l], idx = l;
	for (int i = l+1; i <= r; i++) {
		if(x[i] < min){
			min = x[i];
			idx = i;
		}
	}
	if (min > n) idx = 0;//该范围内全部已被占用
	return idx;
}

void count(int n)//统计每个x(y)可以存在的马的个数 
{
	memset(maze_x, 0, sizeof(maze_x));
	memset(maze_y, 0, sizeof(maze_y));
	for (int i = 0; i < n; i++)
	{
		stat(i, 1);
	}
}

void stat(int i, int op)//统计 
{
	for (int j = arr[0][i]; j <= arr[2][i]; j++)
		maze_x[j] += op;
	for (int j = arr[1][i]; j <= arr[3][i]; j++)
		maze_y[j] += op;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值