HDU 4687 Boke and Tsukkomi 一般图匹配,带花树,思路,输出注意空行 难度:4
http://acm.hdu.edu.cn/showproblem.php?pid=4687
此题求哪些边在任何一般图极大匹配中都无用,对于任意一条边i,设i的两个端点分别为si,ti,
则任意一个极大匹配中都必然有si或ti至少一个点被匹配,当在图中去掉si,ti两个点时,匹配数会损失一个或两个.
如果损失两个,就说明在极大匹配中这两个点分别连接不同的边,于是边i是无用的
所以总体思路:一般图匹配求出最大匹配数cnt0,分别试着去掉每条边的端点,再次匹配,匹配数如果小于cnt0-1,则这条边无用,记录
ATTENTION:如果无用边数为0,仍然需要输出一个空行
带花树思想简介:
对于一对匹配点,设其中一个是S类型点,另外与之配对的是T点,如图
那么对于需要被增广的某个S类型点u,以及与它相连的点v有
1. v是T类型点
2. v还没有被匹配过
3. v是S类型点
三种情况
设match[i]是i的匹配点
对于第1)种情况,即使接上u,v,断开v和match[v],重整整个增广路也不会影响结果,忽略
对于第2)种情况,就像二分图一样,直接接上u,v并增广路取反
对于第3)种情况,有a. v不在当前增广路上 b.v在当前增广路上
对于3.a)情况,把v加入当前增广路,把两条增广路合并,或者说是把v的开花树并入u的
对于3.b)情况,设r为v,u的最近公共祖先,那么r-v-u-r形成了奇环,把奇环缩为一点,这个点就是开花算法的花,设新图为G',原图为G,可以证明G中有对应G的增广路,因为奇数环上任意一点都可以断开形成新增广路,所以都可以作为s点,不过一次只能断开一处
#include <stdio.h> #include <string.h> #include <algorithm> #include <vector> #include <assert.h> using namespace std; const int MAXN = 45; int n;//人数,start from 1 bool del[MAXN];//是否不可用 int e[MAXN][MAXN],sz[MAXN];//图 int match[MAXN];//对应边 bool inQue[MAXN],inPath[MAXN],inBlossom[MAXN];//状态 int que[MAXN],head,tail;//s点加入此队列更新 int start,finish;//增广路取反的开始点,结束点 int newFather;//开花算法 int nxt[MAXN],father[MAXN];//nxt 用于遍历开花树 father 标示所属花 void push(int u){ que[tail] = u; tail++; inQue[u] = true; } int pop(){ int res = que[head]; head++; return res; } int findCommonAncestor(int u,int v){ memset(inPath,false,sizeof(inPath)); while(true){ u = father[u]; inPath[u] = true; if(u == start) break; u = nxt[match[u]]; } while(true){ v = father[v]; if(inPath[v])break; v = nxt[match[v]]; } return v; } void resetTrace(int u){//连环 int v; while(father[u] != newFather){ v = match[u]; inBlossom[father[u]] = inBlossom[father[v]] = true; u = nxt[v]; if(father[u] != newFather) nxt[u] = v; } } void bloosomContract(int u,int v){//连环 newFather = findCommonAncestor(u,v); memset(inBlossom,false,sizeof(inBlossom)); resetTrace(u); resetTrace(v); if(father[u] != newFather) nxt[u] = v; if(father[v] != newFather) nxt[v] = u; for(int tu = 1; tu <= n; tu++) if(inBlossom[father[tu]]){ father[tu] = newFather; if(!inQue[tu]) push(tu); } } void findAugmentingPath(){//增广主过程 memset(inQue,false,sizeof(inQue)); memset(nxt,0,sizeof(nxt)); for(int i = 1;i <= n;i++)father[i] = i; head = tail = 1; push(start); finish = 0; while(head < tail){ int u = pop(); assert(!del[u]); for(int p = 0; p < sz[u]; p++){ int v=e[u][p]; if(!del[v] && (father[u] != father[v]) && (match[u] != v)){//v可用,u,v不在同一花中,u,v不是早已连接 if((v == start) || ((match[v] > 0) && nxt[match[v]] > 0))//奇数环,开花 bloosomContract(u,v); else if(nxt[v] == 0){//合并开花树 nxt[v] = u; if(match[v] > 0) push(match[v]); else{ finish = v;//找到配对点,成功 return; } } } } } } void aug(){//增广路取反 int u,v,w; u = finish; while(u > 0){ v = nxt[u]; w = match[v]; match[v] = u; match[u] = v; u = w; } } void Edmonds(){//增广算法 memset(match,0,sizeof(match)); for(int i = 1; i <= n; i++) if(!del[i]&&match[i] == 0){ start = i; findAugmentingPath(); if(finish > 0)aug(); } } int getMatch(){//统计结果 Edmonds(); int cnt = 0; for(int i = 1; i <= n;i++) if(match[i] > 0) cnt++; return cnt/2; } bool g[MAXN][MAXN]; int from[MAXN*4],to[MAXN*4]; int heap[MAXN*4]; int main(){ int m; while(scanf("%d%d",&n,&m)==2){ memset(g,false,sizeof(g)); memset(del,false,sizeof(del)); memset(sz,0,sizeof(sz)); for(int i = 0;i <m;i++){ scanf("%d%d",from+i,to+i); int f=from[i],t=to[i]; if(!g[f][t]){ g[f][t]=g[t][f]=true; e[f][sz[f]++]=t; e[t][sz[t]++]=f; } } int cnt0 = getMatch(); int ans=0; for(int i = 0;i <m;i++){ int f=from[i],t=to[i]; del[f]=del[t]=true; int cnt = getMatch(); if(cnt == cnt0-2){heap[ans++]=i+1;} del[t]=del[f]=false; } printf("%d\n",ans); for(int i=0;i<ans;i++){ printf("%d%c",heap[i],i==ans-1?'\n':' '); } if(ans==0)puts(""); } return 0; }