原题链接: 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;
}