Description
给出一个N*N的矩阵,其中有K个障碍,每次可以选择删去某一整行或者一整列障碍,求最少的删去障碍次数。
Input
第一行N和K
之后K行每行给出一个障碍的行列坐标。
Ouput
一行,一个整数表示最少次数。
Analysis
目标是把矩阵中的障碍物全部删去。有两个主体,一个是矩阵,另一个是障碍物。
首先站在矩阵的角度考虑。考虑行和列,把一行删去的话可能可以同时删去几个障碍,列也同样。然而,前一次的删去会对之后造成影响,状态就是整个矩阵,用贪心和动态规划都不合适。
换个角度,考虑每个障碍物。每个障碍物都要删去,必然是通过它的行或者列删去的,每个障碍物所连得行或列都至少要占据一个。这里就有了很强的图论背景了,每个障碍物是边,行和列分别是点,则整个图构成了一个二分图,问题就是求至少用多少个点能占据每条边至少一端。这也就是二分图最小覆盖问题。可以证明二分图最小覆盖=二分图最大匹配。直接用匈牙利算法即可。
这是二分图匹配入门题,顺便再概括性的说一说匈牙利算法,实际上是,每次对一个节点u求匹配,能找到就选上。考虑每个u所能连得点v,如果当前与v所连的点能够找到别的替代(这里找新的代替就是个递归过程),则让v与替代相连,而让u与v相连,造成一个两全其美的局面,如此反复下去。更多的关于匈牙利算法,觉得这篇博客讲得很好,挺有意思。https://blog.csdn.net/dark_scope/article/details/8880547
代码:
#include<iostream> #include<string> #include<cstring> #include<vector> #include<stack> #include<algorithm> #include<map> #include<set> #include<queue> #include<sstream> #include<cmath> #include<iterator> #include<bitset> #include<stdio.h> using namespace std; #define _for(i,a,b) for(int i=(a);i<(b);++i) #define _rep(i,a,b) for(int i=(a);i<=(b);++i) typedef long long LL; int readint() { int x; cin >> x; return x; } const int INF = 1 << 30; const int maxn = 505; vector<int>G[maxn]; int n,m; int used[maxn],match[maxn]; bool find(int u){ for(int i=0;i<G[u].size();++i){ int v=G[u][i]; if(!used[v]){ used[v]=1; if(match[v]==0||find(match[v])){ match[v]=u; return true; } } } return false; } int main() { //freopen("C:\\Users\\admin\\Desktop\\in.txt", "r", stdin); //freopen("C:\\Users\\admin\\Desktop\\out.txt", "w", stdout); while(~scanf("%d%d",&n,&m)){ for(int i=1;i<=n;++i)G[i].clear(); for(int i=0;i<m;++i){ int u,v;scanf("%d%d",&u,&v); G[u].push_back(v); } memset(match,0,sizeof(match)); int ans=0; for(int i=1;i<=n;++i){ memset(used,0,sizeof(used)); if(find(i)) ans++; } printf("%d\n",ans); } return 0; }