POJ3041 Asteroids(详细解答)

Time Limit: 1000MSMemory Limit: 65536K
Total Submissions: 33006Accepted: 17512

Description

Bessie wants to navigate her spaceship through a dangerous asteroid field in the shape of an N x N grid (1 <= N <= 500). The grid contains K asteroids (1 <= K <= 10,000), which are conveniently located at the lattice points of the grid.

Fortunately, Bessie has a powerful weapon that can vaporize all the asteroids in any given row or column of the grid with a single shot.This weapon is quite expensive, so she wishes to use it sparingly.Given the location of all the asteroids in the field, find the minimum number of shots Bessie needs to fire to eliminate all of the asteroids.

Input

* Line 1: Two integers N and K, separated by a single space.
* Lines 2..K+1: Each line contains two space-separated integers R and C (1 <= R, C <= N) denoting the row and column coordinates of an asteroid, respectively.

Output

* Line 1: The integer representing the minimum number of times Bessie must shoot.

Sample Input

3 4
1 1
1 3
2 2
3 2

Sample Output

2

Hint

INPUT DETAILS:
The following diagram represents the data, where "X" is an asteroid and "." is empty space:
X.X
.X.
.X.

OUTPUT DETAILS:
Bessie may fire across row 1 to destroy the asteroids at (1,1) and (1,3), and then she may fire down column 2 to destroy the asteroids at (2,2) and (3,2).

详细解答:

本题考查的是最小点覆盖算法。首先对于每一颗小行星(asteroid),从其一行或者一列去摧毁它即可实现摧毁的目的。那么我们可以把每个行、列都看做一个节点,并且行和列分为两个点集,把小行星看做是连接两个点集的边,由此便得到了一个二分图(行与行之间、列与列之间都没有交点)。

至于最小点覆盖的定义则是在一个图中,选最少的点,满足每条边至少有一个端点被选中。这就符合了我们的需求:选最少的行和列(结点),使得连在中间的所有边(小行星)都能被消除。

而最小点覆盖在二分图中有一个特殊的性质:选中点数目等于最大适配中的边数目,下面证明。

首先,适配指的是给定一个二分图G,在图G中的一个子图M中, M的边集{E}中的任意两条边都不交汇于同一个结点(重点);而最大适配指的便是在G中选出最多的边数目,使得任意两条边都不交汇于同一个结点。而找出二分图最大适配的方法则是匈牙利算法(运用增广定理不断找增广路径,这个算法的知识还不明白应该去其他地方先了解,这里不做详细解释)。于是我们从二分图的左点集中每个未盖点(不连接匹配边的点)开始,对于连接它的每条边都去走“增广路径”(不可能是真的增广路径,因为根据最大适配的充要条件,结尾一定是匹配边),然后把路径上的每个结点都标记(包括起点)。最后选出所有左点集中未被标记的结点以及右点集中所有被标记的结点,这些点便构成最小点覆盖。具体原因如下:

1.这个点集中点的数量一定等于最大适配中匹配边的数量。对于上面所说的“增广路径”,其端点一定有一个是未盖点(起点便是),且另一个端点是已盖点(已经连接匹配边的点),否则(两个都是未盖点)真的存在增广路径,违背了最大适配中绝对不存在增广路径的原理。因此对于每一条匹配边,要么两个端点都同时被标记,要么两个都没被标记(比如就是连接孤立的两个分别位于两个点集的结点,这两个点不与其他任何点连接,不会被标记)则选到了它左边的点,要么两个都被标记到了则选到了它右边的点,因此选中的已盖点中肯定是所有已盖点数目的一半(亦即等于匹配边的数量)。而我选的点当中,右边能被标记的点肯定是已盖点(根据刚刚走的“增广路径”的原理),而左边的未盖点显然已经被标记,因此能选中的也必定也是已盖点,即选中的点都是已盖点。所以整个点集中的点就是所有已盖点的一半,数量等同于匹配边的数量。

2.所有边一定至少有一个点为被选中的点。首先对于匹配边上面已经说了,每条都会被选其中一个点。其次对于未匹配的边,其中不可能两个端点都是未盖点(若是则这条边就是一条增广路径),那么对于左端点是未盖点的则显然该边右端点会被标记,即会被选中;而对于右端点为未盖点而左端点为已盖点的,则这个边的左端点一定不会被标记到,若是被标记到了那么从它那里继续走一定能到达这个边的右端点,而起点是左点集的未盖点,即形成新的增广路,矛盾,因此不可能被标记到,由此这个边的左端点一定被选中。综上则所有边都至少有一个端点落在最小点覆盖的点集当中。

3.这个点集一定是最小的,由于最大适配的定义中就保证了任意两条匹配边都不想交与一个结点,因此若想覆盖这些匹配边,至少就要有等于匹配边的数量的结点。

上面洋洋洒洒写了有1200+字,目的只有一个就是详细解答这道题目,由于我看见网上一大堆题解都是千篇一律,直接上代码,思路就两行字,言其然都很难更不说言其所以然,因此非常苦恼,这才下决心写了这么多,也是由于资料难找我上面这些也是总结了很久。只希望有需要帮助的人能看明白吧!

不知道是不是太长了,代码一直是乱码,无奈只能直接放上来了。

#include<iostream>
#include<cstring>
using namespace std;
const int maxn = 505;
bool edg[maxn][maxn], vis[maxn];
int n, k, p[maxn];//p[i]是第i列匹配的行 
bool dfs(int x)
{
    for (int i = 1; i <= n; i++)
    {
        if (edg[x][i] && !vis[i])
        {
            vis[i] = 1;
            if (p[i] == -1 || dfs(p[i]))
            {
                p[i] = x;
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    ios_base::sync_with_stdio(0), cin.tie(0);
    memset(edg, 0, sizeof edg);
    memset(p, -1, sizeof p);
    cin >> n >> k;
    for (int i = 0; i < k; i++)
    {
        int a, b;
        cin >> a >> b;
        edg[a][b] = 1;
    }
    //从行开始去找列适配走增广路 
    int ans = 0;
    for (int i = 1; i <= n; i++)
    {
        memset(vis, 0, sizeof vis);
        if (dfs(i))ans++;
    }
    cout << ans;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值