Asteroids
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).
题目大意:
给定一个NXN的网格
再给定网格上的小行星个数K
接着是对应的K个行星在网格上的坐标。
接着告诉你一次能够消掉某一行或者某一列的所有行星,
问你这样操作的最少次数要多少
题目思路:
把每一个行星看成是对应的图中的边,把左右坐标分别看成水平方向和竖直方向的顶点,那么问题就最终转化成了求能够覆盖所有边的最小顶点集合,即求最小顶点覆盖问题。由于这是二分图,所以最小顶点覆盖问题和最大边匹配是等价的。
代码:
基于最大流的解法:
就是用最大流的解法求解二分图,对应的就是把所有无向边改成有向边,方向从集合U到集合V。增加源点s和汇点t,从s向所有的顶点uU连一条容量为1 的边,从所有顶点v
V向t连一条容量为1 的边。然后运用最大流算法求解即可。
代码:
#include<iostream>
#include<vector>
#include<string.h>
using namespace std;
struct edge{
//创建 目标点,容量,以及反向索引
int to,cap,rec;
edge(int to,int cap,int rec):to(to),cap(cap),rec(rec){};
};
const int INF = 0x3f3f3f3f;
const int MAXV = 505*2;
vector<edge> map[MAXV];
bool used[MAXV];
const int MAXK = 10005;
int R[MAXK];
int C[MAXK];
void add_edge(int from,int to ,int cap){
//from -> to 并添加反向索引 map[to].size() 其实定位的是 from 这个点在 map[to]这个vector的位置
map[from].push_back(edge(to,cap,map[to].size()));
map[to].push_back(edge(from,0,map[from].size()-1));
}
// from起始点,to 目标点 , f 公共容量
int dfs(int from,int to, int f){
if(from == to ) return f;
//避免重复访问
used[from] = true;
for(int i = 0;i<map[from].size();i++){
//注意这里添加的是引用,保证图的残差网络能够计算并保存
edge &e = map[from][i];
if(e.cap>0 && !used[e.to]){
int d = dfs(e.to,to,min(f,e.cap));
if(d>0){
//开始计算反向边,构造残差网络,目的在于能够继续找到增广路
e.cap-=d;
map[e.to][e.rec].cap+=d;
return d;
}
}
}
return 0;
}
int solve(int s,int t){
int max_flow = 0;
while(true){
memset(used,0,sizeof(used));
int d = dfs(s,t,INF);
//最后增广路已经找不到了就返回结果
if(d==0) return max_flow;
max_flow+=d;
}
return max_flow;
}
int main(){
int N,K;
scanf("%d%d",&N,&K);
for(int i=0;i<K;i++){
scanf("%d%d",&R[i],&C[i]);
}
//R[i] _0,N-1
//C[i] _N,N*2-1
//s N*2
//构造源点和汇点
int s = N*2;
int t = s+1;
//边的添加方式要注意,不能重复添加
//这种方式很容易写出来,要注意这样添加边会有重复 因为某个定点可能会出现多次
//for(int i=0;i<N;i++) add_edge(s,R[i]-1,1);
for(int i=0;i<N;i++) add_edge(s,i,1);
for(int i=0;i<N;i++) add_edge(N+i,t,1);
for(int i=0;i<K;i++){
add_edge(R[i]-1,N+C[i]-1,1);
}
int res = solve(s,t);
printf("%d\n",res);
return 0;
}
匈牙利算法:
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
int N,K;
const int MAXV = 505;
const int MAXK = 10005;
int R[MAXK],C[MAXK];
int match[MAXV*2];
int used[MAXV*2];
vector<int> G[MAXV*2];
int V;
void add_edge(int from,int to){
G[from].push_back(to);
G[to].push_back(from);
}
bool dfs(int v){
used[v] = true;
for(int i=0;i<G[v].size();i++){
int u = G[v][i];
int w = match[u];
if(w<0 || !used[w]&& dfs(w)){
match[v] = u;
match[u] = v;
return true;
}
}
return false;
}
int solve(){
int res = 0;
memset(match,-1,sizeof(match));
for(int i=0;i<V;i++){
if(match[i]<0){
memset(used,0,sizeof(used));
if(dfs(i)){
res++;
}
}
}
return res;
}
int main(){
scanf("%d%d",&N,&K);
for(int i=0;i<K;i++){
scanf("%d%d",&R[i],&C[i]);
}
V = 2*N;
for(int i=0;i<K;i++){
add_edge(R[i]-1,N+C[i]-1);
}
int res = solve();
printf("%d\n",res);
return 0;
}