这道题实现起来还是比较简单的,但是理解起来可能有点困难。
我最开始想到的是贪心法,每次消灭当前小行星最多的一行或一列。然而WA了。Discuss区里已经有高人给出反例。
下面给出正确的解法
我们把行和列抽象成点,把小行星抽象成边,每出现一个小行星,就把其行列所对应的点连起来。这样就形成了一个无向图$G=\left(V, E\right)$。问题就转化为了求这个图G中的最小点覆盖,即求一个元素数量尽可能小的点集$V‘ \subset V$,$E$中的所有边均与其内的一点相连。
最小点覆盖问题是一个NP困难问题,目前没有已知的能够在多项式时间内解决该问题的算法。
不过我们可以这样考虑:
我们给出一个定义:设存在一个集合${V}_{1} \subset V, ~ {V}_{2} = V - {V}_{1}$。若$a \in {V}_{1}, ~ b \in {V}_{2}, ~ (a, b) \in E$,那么我们称这条边$(a, b)$为一个匹配。
若有两个点集${V}_{1} \subset V, ~ {V}_{2} = V - {V}_{1}$,两者并不相交。若有一种方案,使得${V}_{1}$和${V}_{2}$的所有匹配之间均没有重复的点,且${V}_{1}$内的所有点均拥有至少一个匹配。则当${V}_{1}$的元素数量最大时,${V}_{1}$就是这张图的最小点覆盖。
我们这里做一个简单的不严谨的证明,便于理解:
- ${V}_{1}$的元素数量没有达到最大,意味着至少有一条边,它的两个端点都不在${V}_{1}$中。显然${V}_{1}$不是这张图的一个点覆盖,因为它不符合点覆盖的定义。
- 假设${V}_{1}$是这张图的一个点覆盖。如果${V}_{1}$中存在一个点$v$,它在${V}_{2}$中没有满足上述条件的对应点。那么要么没有边与该点相连,要么与其相连的边的另一个端点全在${V}_{1}$内。不论如何,我们都可以把$v$从${V}_{1}$中去掉,其仍然是这张图的一个点覆盖。换句话说,此时的${V}_{1}$并不是最小点覆盖。
- 假设${V}_{1}$是这张图的一个点覆盖。${V}_{2} = V - {V}_{1}$。
综上所述,若存在某一条边,其两个点均不在${V}_{1}$中,那么${V}_{1}$就不是这张图的点覆盖。若${V}_{1}$是此图的一个点覆盖,其中存在某个点,它在${V}_{2} = V - {V}_{1}$中没有边与其相连,那么${V}_{1}$不是该图的最小点覆盖。若没有一种方案,使得所有的匹配之间都没有重复的点,那么${V}_{1}$也不是该图的最小点覆盖。
图中的所有点要么在${V}_{1}$中,要么在${V}_{2} = V - {V}_{1}$中。我们每找到一条两点$(a, b)$均不在${V}_{1}$中的边,就将$a$从${V}_{2}$中移入${V}_{1}$,将这条边记作一个匹配。当然,如果有必要,我们也可以将$a$移回${v}_{2}$,再将$b$移进${V}_{1}$;或者将$a$和$b$都移回${V}_{2}$,删除这条匹配。若我们所找到的匹配没有达到最大数量,则意味着还存在有至少一条边,它的两个点均不在${V}_{1}$中。此时的${V}_{1}$并不是这张图的点覆盖。
并且,根据证明3,我们这里所找到的匹配,它们中任意两条之间不能拥有重复的点。
而当我们找到的符合条件的边(也就是匹配)的数量最大时,${V}_{1}$刚好变成了这张图的一个点覆盖,并且,它时这张图的最小点覆盖。
这时,找点的问题被转化成了找边的问题。
在这个具体的问题里,我们所找到的符合条件的边,其两端点必定一个代表行,一个代表列。也就是说,如果我们将$V$划分为代表行的点集${V}_{r}$和代表列的点集${V}_{c}$,则所有的匹配一定是跨越了这两个集合的。
这样,所有的边$(a, b)$不仅是${V}_{1}$、${V}_{2}$之间的匹配,也是${V}_{r}$和${V}_{c}$间的匹配。并且,我们这里所找到的所有的边,它们的点都不重复(否则就违反了刚才的证明3)。因此,找边的问题就变成了找二分匹配的问题。找到的边最大就变成了找到的二分匹配最大。
这个问题就被转化成了一个最大二分匹配问题。
最大二分匹配问题,可以用网络流的手段解决,也可以用匈牙利算法解决。匈牙利算法是一种专门用来解决二分匹配问题的算法,在这里就不具体解释了。不过这道题也是我第一次用这个算法解决问题,错了很多次。
1 #include <cstddef> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cstring> 5 #include <iostream> 6 7 int n, k; 8 9 bool dfs_visited[510]; 10 int graph[510][510]; 11 int matches[510]; 12 13 bool Hungarian_Aux(int vertex) { 14 15 for(int i = 1; i <= n; ++i) { 16 if(!graph[vertex][i] || dfs_visited[i]) 17 continue; 18 dfs_visited[i] = true; 19 if(matches[i] == 0 || Hungarian_Aux(matches[i])) { 20 matches[i] = vertex; 21 return true; 22 } 23 } 24 25 return false; 26 } 27 28 int Hungarian_Algorithm() { 29 int matches_count = 0; 30 for(int vertex = 1; vertex <= n; ++vertex) { 31 memset(dfs_visited + 1, 0, n * sizeof(bool)); 32 if(Hungarian_Aux(vertex)) { 33 ++matches_count; 34 } 35 } 36 37 return matches_count; 38 } 39 40 41 int main() { 42 scanf("%d%d", &n, &k); 43 int r, c; 44 for(int i = 1; i <= k; ++i) { 45 scanf("%d%d", &r, &c); 46 graph[r][c] = true; 47 } 48 printf("%d\n", Hungarian_Algorithm()); 49 50 return 0; 51 }