poj 2942 Knights of the Round Table 点双连通分量+交叉染色法

题意:亚瑟王要在圆桌上召开骑士会议,为了不引发骑士之间的冲突,并且能够让会议的议题有令人满意的结果,每次开会前都必须对出席会议的骑士有如下要求:
1、  相互憎恨的两个骑士不能坐在直接相邻的2个位置;
2、  出席会议的骑士数必须是奇数,这是为了让投票表决议题时都能有结果。

 如果出现有某些骑士无法出席所有会议(例如这个骑士憎恨所有的其他骑士),则亚瑟王为了世界和平会强制把他剔除出骑士团。现在给定准备去开会的骑士数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.


你可能感兴趣的:(poj 2942 Knights of the Round Table 点双连通分量+交叉染色法)