原题链接
光束的攻击选择可以是横坐标从
x
=
1
x=1
x=1到
x
=
N
x=N
x=N和纵坐标从
y
=
1
y=1
y=1到
y
=
N
y=N
y=N, 一共
2
N
2N
2N种。显然,同样的选择没有必要执行多次,而攻击的顺序对结果没有影响,所以总的攻击方案共有
2
2
N
2^{2N}
22N。我们只要在这个解空间中,寻找能够摧毁所有小行星的最小的解就可以了。要破坏某个小行星,只能通过对应水平方向或竖直方向的光束的攻击。利用攻击方法只有两种这一点,我们可以将问题按如下方法转换为图。
把光束当作图的顶点,而把小行星当作连接对应光束的边。这样转换之后,光束的攻击方案即对应一个顶点集合 而要求攻击方案能够摧毁所有的小行星,也就是图中的每条边都至少有一个属于
S
S
S的端点。这样一来,问题就转为了求最小的满足上述要求的顶点集合
S
S
S。
这正是最小顶点覆盖的问题。之前我们已经介绍过,最小顶点覆盖问题通常是NP困难的,不过在二分图中等于最大匹配,因而可以高效地求解。事实上,本题中所有顶点可以分成水平方向和竖直方向的攻击选择两类,而每颗小行星所对应的边都分别与一个水平方向和一个竖直方向的顶点相连,所以是二分图。因此,只要运用二分图最大匹配算法,问题就会迎刃而解了。二分图匹配
#include<iostream>
#include<cmath>
#include<string.h>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<iomanip>
using namespace std;
#define ll long long
#define vec vector<ll>
#define arr vector<vec>
#define Max 1005
ll n, m, V, used[Max], ans = 0, R[Max], edge[Max][Max];
//贝尔曼算法求二分图最大匹配
bool match(ll r) {
for (ll i = 1; i <= n; i++) {
if (edge[r][i] && !used[i]) {
used[i] = 1;
if (!R[i] || match(R[i])) {
R[i] = r; return true;
}
}
}
return false;
}
int main() {
cin >> n >> m; V = 2 * n;
for (ll i = 1; i <= m; i++) {
ll a, b; cin >> a >> b;
edge[a][b] = 1;
}
for (ll i = 1; i <= n; i++) {
memset(used, 0, sizeof(used));
if (match(i)) ans++;
}
cout << ans << endl;
}