UVa11134 Fabled Rooks
题目大意
在一个\(n\times n(1<=n<=5000)\)的棋盘上放置n个车,每个车都只能在给定的一个矩形(\(xl_i,xr_i,yl_i,yr_i\))里放置,使其n个车两两不在同一行和同一列,判断并给出解决方案。
题解
一道神奇的贪心题……
这道题乍一看感觉难以下手,机房大佬一看这题竟说出了二分图匹配(当时我震惊了😨)。
首先,我们发现其实x轴与y轴是互不相关的,可以独立求解。于是我们把x轴与y轴分开求解。
于是问题就变成了:在\([1,n]\)的区间中,有一些区间,在每一个区间中选一个点,使最终恰好覆盖\([1,n]\)中的这\(n\)个点。
开始时,我想的是以\(l\)作为第一关键字,\(r\)作为第二关键字进行排序。然后从左往右扫。然而这样显然是不成立的,[1,3],[1,3],[2,2]
这组数据就会被卡掉。
在用贪心法解决问题时,我们可以考虑:如果要选择几种状态,一种决策的“后路”覆盖了所有其他决策的“后路”,那我们不应当选择这种决策。(可能我语文不怎么好,那就用这道题解释一下吧。)
试想一下,如果我们要求从前往后做出抉择,且有两段区间都可以选择,那我们应该选择哪一段?显然是\(r\)值小的哪一段。应为对于后面的点,\(r\)值小的可以覆盖的点\(r\)值大的也可以覆盖,而\(r\)值大的可覆盖的点\(r\)值小的可能无法覆盖。这样,我们可以认为\(r\)值大的”后路“覆盖了\(r\)值小的的所有后路,于是我们应该选\(r\)值小的。
于是我们就不难想到方法:以\(r\)(后路)作为关键字排序(\(r\)相同的可以随意排),然后对于每一个序列,从\(l\)到\(r\)扫描。如果是该点没有被选择过,那就选择该点。
具体实现可以看一下代码。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 5005;
struct qujian
{
int xl, xr, yl, yr;
int x, y;
int num;
inline bool operator < (const qujian& oth) const//重载小于号,方便打乱之后排回来
{
return num < oth.num;
}
} qj[maxn];
inline bool cmp_x(qujian a, qujian b)//以r为关键字进行排序
{
return a.xr < b.xr;
}
inline bool cmp_y(qujian a, qujian b)
{
return a.yr < b.yr;
}
bool have[maxn];
inline void solve(int n)
{
for(int i = 1; i <= n; ++i)
{
scanf("%d%d%d%d", &qj[i].xl, &qj[i].yl, &qj[i].xr, &qj[i].yr);
qj[i].num = i;//在读入时记录标号,方便打乱之后排回来
}
memset(have, 0, sizeof(have));//多组数据,注意初始化
sort(qj+1, qj+n+1, cmp_x);
for(int i = 1; i <= n; ++i)
{
int x = qj[i].xl;
while(have[x] && x <= qj[i].xr)//找到第一个没有被选过的点
x++;
if(x > qj[i].xr)
{
puts("IMPOSSIBLE ");//udebug上有空格,但不加似乎也能过
return;
}
else
{
qj[i].x = x;
have[x] = true;
}
}
memset(have, 0, sizeof(have));
sort(qj+1, qj+n+1, cmp_y);
for(int i = 1; i <= n; ++i)
{
int y = qj[i].yl;
while(have[y] && y <= qj[i].yr)
y++;
if(y > qj[i].yr)
{
puts("IMPOSSIBLE ");
return;
}
qj[i].y = y;
have[y] = true;
}
sort(qj+1, qj+n+1);//排回来
for(int i = 1; i <= n; ++i)
printf("%d %d\n", qj[i].x, qj[i].y);
}
int main()
{
int n;
while(scanf("%d", &n) == 1 && n)
solve(n);
}