- 首先最小覆盖集可以用二分图匹配求出来记为ans。
然后是怎么来求最小覆盖集。
首先枚举每个已经匹配了的点,如果去掉这个点以及和这个点所相连的所有边, 如果剩下图的二分图的最大匹匹配如果是ans-1,那么这个点就是最小覆盖集中的点,如果最大匹配还是ans,那么这个点就不是最小覆盖集点,因为如果选这个点,那么新加入的那个点肯定是在去掉的这个点一边,但是他是不能被去掉的这个点所覆盖。
接下来是怎么求解最小字典序,从0到n-1 ,如果找到一个点是覆盖点,那么就把他加入最小覆盖点集中,并去掉和这个点所连的所有边。
其实这道题并不用每次都找匹配点,因为,我们去掉一个点的话,那么用原来和他匹配的点找增广路,那么如果找到新的增广路,而原来的 点不便,那么最大匹配还是ans-1 ,所以这样的话可以极大的节约时间。
可以看看这边文章http://blog.csdn.net/mofixroot/archive/2009/10/20/4706113.aspx
别人给我的思路
首先构图跑SAP(匈牙利应该也行,但我对匈牙利的实现细节不了解(我从未实现过匈牙利),所以不敢妄加说明)求出最小点覆盖的数量……这个没问题吧……
然后把匹配边、哪些端点被匹配了也都搞出来……这个也没问题吧……
然后我们就能发现,在每条匹配边中,有且仅有一个端点会被入选到最终结果中。
我们不妨思考一下,一个端点可以进入最终结果的条件是什么?可以发现,如果这个点是被匹配点,且删掉这个点和与其相连的所有边后的新图最小点覆盖数目比当前的数目小1,那么这个点是有资格作为最小覆盖中的点的,否则一定不能。
接下来的事情就是字典序的问题了。我们不妨从1~n枚举点,如果这个点有资格进入最小点覆盖集,就让他进入,然后删掉这个点以及和它相连的所有边。继续这个过程,显然最后得到的就是字典序最小的点覆盖集了。
事 实上,真正实现时大可不必这么麻烦。在判定一个点是否可行时,直接给这条匹配边的另一个端点一个单位流量,然后判定可否增广(虚拟的增广,一个dfs即 可)。如果可以增广,这个点就不能进入最小点覆盖集,收回刚才给的流量,否则这个点进入最终答案,那个流量不必收回,因为这时另一个匹配点还是可以被其他 点匹配的。
时间复杂度:匹配的复杂度:O(sqrt(n)*m),得出最终答案的复杂度:O(n*m),总:O(nm)
具体见代码。
#include<iostream>//poj3715 博客里面有说明 #include<algorithm> #include<cstring> #include<sstream> #include<assert.h> #include<cmath> #include<vector> #include<string> #include<map> #include<set> #include<queue> #include<stack> using namespace std; typedef long long ll; const int inf=1000000000; const double pi=acos(-1.0); #define eps (1e-15) #define L(x) ((x)<<1) #define R(x) ((x)<<1|1) #ifdef DBG #define see(x) (cerr<<"[Line : "<< __LINE__<<"] : "<<#x<<"="<<x<<endl) #define se(x) cerr<<x<<" " #else #define see(x) // #define se(x) // #endif #define maxn 230 int n,m; int g[maxn][maxn]; int a[maxn]; int fugai[maxn],visit[maxn],prex[maxn],prey[maxn]; void init() { memset(g,0,sizeof(g)); memset(a,0,sizeof(a)); memset(prex,-1,sizeof(prex)); memset(prey,-1,sizeof(prey)); memset(fugai,0,sizeof(fugai)); } int canx(int x) { if(fugai[x]==1) return 0; int i,j; for(i=0; i<n; i++) if(g[x][i] && visit[i]==0 && fugai[i]==0) { visit[i]=1; if(prey[i]==-1 || canx(prey[i])) { prex[x]=i; prey[i]=x; return 1; } } return 0; } int cany(int y) { if(fugai[y]==1) return 0; int i,j; for(i=0; i<n; i++) if(g[y][i] && visit[i]==0 && fugai[i]==0) { visit[i]=1; if(prex[i]==-1 || cany(prex[i])) { prey[y]=i; prex[i]=y; return 1; } } return 0; } void solve() { int i,j,res=0; for(i=0; i<n; i++) if(a[i]==0) //匈牙利之用从二分图的一边,搜索 { memset(visit,0,sizeof(visit)); res+=canx(i); } printf("%d",res); int x,y; for(i=0; i<n; i++) { if(a[i]==0)//覆盖集中的点可以是x的点也可以是y的点 { if(prex[i]==-1)continue;//如果不是原来已经匹配的边 y=prex[i];//从原来和他匹配的边找增广路 prex[i]=-1; prey[y]=-1; fugai[i]=1; memset(visit,0,sizeof(visit)); if(cany(y)==1)//如果找到增广路那么,这个点就不是覆盖集中的点 { fugai[i]=0; //prex[i]=y; //prey[y]=i; } else printf(" %d",i); } else { if(prey[i]==-1) continue; x=prey[i]; prey[i]=-1; prex[x]=-1; fugai[i]=-1; memset(visit,0,sizeof(visit)); if(canx(x)==1) { fugai[i]=0; //prey[i]=x; //prex[x]=i; } else printf(" %d",i); } } puts(""); } int main() { #ifndef ONLINE_JUDGE freopen("in","r",stdin); #endif int t,i,j,k; scanf("%d", &t); while(t--) { scanf("%d%d", &n,&m); init(); int x,y; for(i=0; i<n; i++) { scanf("%d", &x); a[i]=x; } for(i=0; i<m;i++) { scanf("%d%d", &x,&y); if(a[x]!=a[y]) { if(a[x]==1) swap(x,y); g[x][y]=1;//建图 g[y][x]=1; } } solve(); } }
2011-04-2120:22:25