如果出现有某些骑士无法出席所有会议(例如这个骑士憎恨所有的其他骑士),则亚瑟王为了世界和平会强制把他剔除出骑士团。现在给定准备去开会的骑士数n,再给出m对憎恨对(表示某2个骑士之间使互相憎恨的),问亚瑟王至少要剔除多少个骑士才能顺利召开会议?
分析:想一想,如果骑士A可以做到圆桌旁,得满足两个条件:1. 圆桌上的有奇数个人。2. 圆桌每两个相邻的骑士不hate对方。就可以建一个补图,顶点集合为所有的骑士,如果任意一对骑士可以坐在一起,则可以在图上加上一条边。则如果能找到一个奇圈,则这个圈里的所有人都符合条件,都可以留下。如果某一个骑士,不在任何一个奇圈内,则会被国王开除。
既然是圈,就表示这个圈所表示的子图是个双连通的,而又有一个定理是这样描述的,对于一个(点)双连通分量,如果找到一个奇圈,则这个分量的其他点也必然在某一个奇圈内。因为每个不在那个奇环里的点都一定能找到两条连到奇环上不同节点的路径,然后奇环被分割为一个总数为奇数的节点串和一个总数为偶数的节点串,这时做出正确的选出和那一串构成环即可形成奇环。
接着用交叉染色法判断是不是二分图,一个图是二分图是不存在奇环的充要条件。
这样就好做了,只要找到图的割点,找到所有的双连通分支,判断一下分支中是否存在奇圈,如果存在,则整个分支的点都可以标记为留下,最后那总的点个数,减去可以留下的,剩下的就是要被国王开除的。
求点双连通分量的方法:
首先,用tarjan算法,dfs遍历全图,用dfs_dep数组记录每个点在遍历过程中的深度,用low_point数组记录每个点的邻居中(不包括父亲)深度最浅的节点,把遍历过程中所有树枝边入栈。我们在遍历过程中,对于一个节点u,如果在遍历完成它的某子节点v之后,发现low_point[v]==dfs_dep[u]则说明u与v及其子孙构成一个点双连通分量,我们不停弹栈直到边(u,v)被弹出,和这些边相关的点构成一个点双连通分量。当我们遍历完点u的所有子孙之后,若发现low_point[u]==dfs_dep[u],则说明u不会再与其祖宗节点构成点双连通分量,但此时还有一条u的父亲和u的连边存在于栈顶,我们要把它弹出并丢弃。
代码:
var top,e,n,m,t:longint; stack:array[1..2000,1..2] of longint; last,color,low,dfn:array[1..1000] of longint; ans,v:array[1..1000] of boolean; side:array[1..2000000] of record x,y,next:longint; end; a:array[1..1000,1..1000] of boolean; procedure add(x,y:longint); begin inc(e); side[e].x:=x; side[e].y:=y; side[e].next:=last[x]; last[x]:=e; inc(e); side[e].x:=y; side[e].y:=x; side[e].next:=last[y]; last[y]:=e; end; procedure init; var x,y,i,j:longint; begin fillchar(a,sizeof(a),false); for i:=1 to m do begin readln(x,y); a[x,y]:=true; a[y,x]:=true; end; e:=0; fillchar(last,sizeof(last),0); for i:=1 to n-1 do for j:=i+1 to n do if a[i,j]=false then add(i,j); end; function min(x,y:longint):longint; begin if x<y then exit(x) else exit(y); end; function fill(x:longint):boolean; var i:longint; begin fill:=true; i:=last[x]; while i>0 do with side[i] do begin if v[y] then if color[y]=-1 then begin color[y]:=1-color[x]; exit(fill(y)); end else if color[y]=color[x] then exit(false); i:=next; end; end; procedure check(x:longint); var i:longint; begin for i:=1 to n do color[i]:=-1; color[x]:=0; if not fill(x) then for i:=1 to n do if v[i] then ans[i]:=false; end; procedure tarjan(x,fa:longint); var i:longint; begin inc(t); dfn[x]:=t; low[x]:=t; if fa<>-1 then begin inc(top); stack[top,1]:=fa; stack[top,2]:=x; end; i:=last[x]; while i>0 do with side[i] do begin if y=fa then begin i:=next; continue; end; if dfn[y]=0 then begin tarjan(y,x); low[x]:=min(low[x],low[y]); if low[y]=dfn[x] then begin fillchar(v,sizeof(v),false); while true do begin v[stack[top,1]]:=true; v[stack[top,2]]:=true; dec(top); if (stack[top+1,1]=x)and(stack[top+1,2]=y) then break; end; check(x); end; end else low[x]:=min(low[x],dfn[y]); i:=next; end; if (dfn[x]=low[x])and(top>0) then dec(top); end; procedure main; var i:longint; begin fillchar(dfn,sizeof(dfn),0); fillchar(low,sizeof(low),0); fillchar(ans,sizeof(ans),true); top:=0; t:=0; for i:=1 to n do if dfn[i]=0 then tarjan(i,-1); end; procedure print; var s,i:longint; begin s:=0; for i:=1 to n do if ans[i] then inc(s); writeln(s); end; begin readln(n,m); while n+m>0 do begin init; main; print; readln(n,m); end; end.