Описание
Задан неориентированный граф с N вершинами, пронумерованными целыми числами от 1 до N

Задание
Напишите программу, которая последовательно решает следующие задачи:

A)  Выясняет количество компонент связности графа
Б)  Находит и выдает все такие ребра, что удаление любого из них ведет к увеличению числа компонент связности
B)  Определяет, можно ли ориентировать все ребра графа таким образом, чтобы получившийся граф оказался сильно связным
Г)  Ориентирует максимальное количество ребер, чтобы получившийся граф оказался сильно связным
Д)  Определяет минимальное количество ребер, которые следует добавить в граф, чтобы ответ на пункт В был утвердительным


Входные данные
Во входном файле записано целое число N (1
N100) и список ребер графа, заданных номерами концевых вершин.

Выходные данные
Ваша программа должна вывести в выходной файл последовательно ответы на пункты А-Д в следующем формате:

  • В первой строке запишите ответ на пункт А

  • Во второй строке запишите количество ребер из ответа на пункт Б, а в последующих строках выдайте сами эти ребра

  • В следующую строку выведите сообщение «NOT POSSIBLE», если требуемым в пункте В способом ориентировать граф невозможно, иначе выведите сообщение «POSSIBLE» (ориентированный граф называется сильно связным, если из любой его вершины можно пройти в любую другую, двигаясь по ребрам вдоль стрелок)

  • Далее выведите максимальное количество ребер графа, которые можно ориентировать (пункт Г). В последующие строки выведите список этих ребер

  • В качестве ответа на пункт Д выведите количество ребер, которые следует добавить в исходный граф, а далее выведите сами эти ребра

Ребра задаются указанием номеров своих концевых вершин, а при выводе ответа на пункт Г должна быть указана их ориентация (вначале выводится номер начальной вершины, затем - номер конечной). Если ответ на пункт А отличен от единицы, то пункты В и Г решать не следует и ответы на них не выводятся. Баллы за пункт В в случае утвердительного ответа на него начисляются лишь в том случае, если программа правильным образом ориентировала ребра графа (пункт Г)


Например:

ORIENTGR.IN
4
1 2
2 4
3 4
4 1


ORIENTGR.OUT
1
1
3 4
NOT POSSIBLE
3
1 2
2 4
4 1
1
1 3






Идеи
Обход в глубину, поиск компонент реберной двусвязности

Комментарии
Ребро, удаление которого увеличивает число компонент связности графа, называется мостом. Если граф не содержит мостов, то он называется реберно-двусвязным. Ясно, что произвольный связный граф разбивается на компоненты реберной двусвязности (т.е. максимальные реберно-двусвязные подграфы), соединенные мостами [Емеличев 90, п.34]. При этом каждая вершина принадлежит в точности одной компоненте и каждое ребро, не являющееся мостом, входит только в одну компоненту. Описанное разбиение графа может быть осуществлено с помощью обхода в глубину, аналогично поиску обычных компонент двусвязности графа [Липский 88, п.2.6].

Понятно и то, что граф, содержащий мост, нельзя ориентировать требуемым в пункте В способом. С другой стороны, любой реберно-двусвязный граф ориентировать таким образом можно. Для этого нужно выполнить обход в глубину из произвольной вершины r, ориентируя все ребра графа, принадлежащие глубинному остовному дереву, по направлению от r, а все ребра, ему не принадлежащие, - по направлению к r (покажите, что полученный граф будет сильно связным). Таким образом, ответ на пункт В положителен в том и только том случае, когда граф не содержит мостов

Теперь ясно, что в пункте Г нужно ориентировать все реберно-двусвязные компоненты, а все мосты оставить неориентированными. Следовательно, пункты А-Г могут быть решены все вместе сравнительно простой модификацией обхода в глубину

Перейдем теперь к менее очевидному пункту Д. Пусть заданный граф связен. Будем рассматривать его структуру "с точностью до реберно-двусвязных компонент", стянув каждую такую компоненту в одну новую "супервершину". При этом мы получим связный ациклический граф, т.е., попросту говоря, дерево (эта процедура стягивания компонент очень похожа на построение конденсации графа [Кристофидес 78, п.2.3]). Обозначим через k количество висячих (т.е. степени 1) вершин этого дерева. Процесс добавления к исходному графу ребер требуемым в пункте Д способом можно мыслить как добавление ребер к рассматриваемому дереву. Поскольку в итоге не должно остаться ни одной висячей вершины, то к графу необходимо добавить не менее [k/2] ребер

С другой стороны, такого количества ребер достаточно. Докажем это. Утверждение очевидно для деревьев с k=2 и k=3. Рассмотрим теперь добавление к дереву с k>3 ребра, соединяющего две его висячие вершины u и v. После добавления образуется граф с одним циклом (т.е. реберно-двусвязной компонентой), который мы также стянем, получив новое дерево. Если вершина, полученная в результате этого стягивания, в новом дереве не является висячей, значит добавленное ребро уменьшило количество висячих вершин на две. В противном случае назовем висячую вершину v "плохой" для u (а вершину u - "плохой" для v), и для сокращения числа висячих вершин на две нам следует искать другую пару (u,v). Однако оказывается, что для каждой висячей вершины u может существовать не более одной плохой висячей вершины v (докажите это самостоятельно)

Таким образом, всегда можно добавить ребро, уменьшающее число висячих вершин на две. Это завершает доказательство сформулированного утверждения. Случай, когда исходный граф несвязен, разбирается аналогично


 


Решение

{$A+,B-,D+,E-,F-,G+,I+,L+,N+,O-,P-,Q-,R-,S-,T-,V+,X+,Y+}

{$M 63384,0,655360}
 
program Suh;
 
type TarrI = array [1..10000] of integer;
     ParrI = ^TarrI;
 
var n: integer;
    m: integer;
    lb, le: array [1..10000] of integer;
    time: longint absolute $0000:$046C;
    times: longint;
    lc: integer;
    fl, ll: array [0..100] of integer;
    vv,v: array [1..100] of integer;
    badr: ParrI;
    g: array [1..100, 1..100] of shortint;
    rb, re: array [1..300] of integer;
    rc: integer;
    TreeB: array [0..100] of integer;
    Trees: integer;
    OneLeaf: array [0..100] of integer;
    cv, bc: integer;
    comps: integer;
    Forbb, Forbe: integer;
    ref: array [1..100] of integer;
    Leafs: integer;
    LeafsThere: array [1..100] of integer;
    Bral: array [1..100] of integer;
 
procedure ReadAll;
var i, j: integer;
begin
  assign (input, 'orientgr.in');
  reset (input);
  readln (n);
  lc := 0;
    while not SeekEof (input) do
    begin
      read (input, i, j);
      inc (lc);
      lb [lc] := i;
      le [lc] := j;
      inc (lc);
      lb [lc] := j;
      le [lc] := i;
    end;
  close (input);
end;
 
procedure SortLinks (l, r: integer);
var i, j, x, y: integer;
begin
  i := l; j := r; x := lb [(l+r) div 2];
  repeat
    while lb [i] < x do inc (i);
    while x < lb [j] do dec (j);
    if i <= j then
     begin
       y := lb [i]; lb [i] := lb [j]; lb [j] := y;
       y := le [i]; le [i] := le [j]; le [j] := y;
       inc (i);
       dec (j);
     end;
  until i > j;
  if l < j then SortLinks (l, j);
  if i < r then SortLinks (i, r);
end;
 
 
procedure PrepareLinks;
var i, j, q: integer;
begin
  SortLinks (1, lc);
  q := 0;
  for i := 1 to lc do
    while lb [i] > q do
      begin
        ll [q] := i-1;
        inc (q);
        fl [q] := i;
      end;
  ll [q] := lc;
  for i := 1 to n do if fl[i] = 0 then fl [i] := lc+3;
end;
 
procedure Bulk (a: byte);
var i: integer;
begin
  v[a] := 1;
  for i := fl [a] to ll[a] do
  if v[le[i]] = 0 then
     if not ((a = ForbB) and (le[i] = ForbE)) then
     if not ((a = ForbE) and (le[i] = ForbB)) then
     Bulk (le[i]);
end;
 
function FindComps: integer;
var i, c: integer;
begin
  for i := 1 to n do v[i] := 0;
  c := 0;
  for i := 1 to n do
  if v[i] = 0 then
   begin
     inc (c);
     bulk (i);
   end;
  FindComps := c;
end;
 
procedure TaskA;
begin
  ForbB := 0; ForbE := 0;
  Comps := FindComps;
  writeln (Comps);
end;
 
procedure TaskB;
var i: integer;
    c: integer;
label Fail;
begin
  bc := 0;
  if lc > ((n-2) * (n-1)) then goto Fail;
  for i := 1 to lc do
  if lb[i] < le[i] then
  begin
    if Time-Times > 127 then goto Fail;
    ForbB := lb [i];
    ForbE := le [i];
    c := FindComps;
    if c > Comps then
      begin
        inc (bc);
        badr^[bc] := i;
      end;
  end;
Fail: writeln (bc);
    for i := 1 to bc do
      writeln (lb[badr^[i]], ' ',le[badr^[i]]);
end;
 
procedure TaskV;
begin
  if Comps <> 1 then exit;
  if bc = 0 then writeln ('POSSIBLE') else writeln ('NOT POSSIBLE');
end;
 
procedure DirSo (x, y: integer);
begin
  writeln (x, ' ', y);
  g[x, y] := 0;
  g[y, x] := 0;
end;
 
procedure SearchDeep (a: integer);
var i: integer;
begin
  v[a] := 1;
  for i := fl [a] to ll [a] do
  if g[a, le[i]] <> 0 then
      if v[le[i]] = 0 then
         begin
           DirSo (a, le[i]);
           SearchDeep (le[i]);
         end else
         begin
           DirSo (a, le[i]);
         end;
end;
 
procedure Orient (a: integer);
begin
  v[a] := a;
  SearchDeep (a);
end;
 
procedure BuildGraph;
var i, j: integer;
begin
  FillChar (g, sizeof (g), 0);
  for i := 1 to lc do
    g[lb[i], le[i]] := 1;
end;
 
procedure KillBad;
var i, x, y: integer;
begin
  for i := 1 to bc do
  begin
    x := lb[badr^[i]];
    y := le[badr^[i]];
    g [x, y] := 0;
    g [y, x] := 0;
  end;
end;
 
 
procedure TaskG;
var i: integer;
begin
  if Comps <> 1 then exit;
  writeln ((lc div 2)-bc);
  BuildGraph;
  KillBad;
  for i := 1 to n do
    v[i] := 0;
  for i := 1 to n do
    if v[i] = 0 then Orient (i);
end;
 
procedure Fill (a, c: integer);
var i: integer;
begin
  v[a] := c;
  for i := fl [a] to ll [a] do
  if g[a, le[i]] <> 0 then
      if v[le[i]] = 0 then Fill (le[i], c);
end;
 
procedure BuildCondensedGraph;
var i: integer;
begin
 FillChar (g, sizeof (g), 0);
  for i := 1 to lc do
    g[vv[lb[i]], vv[le[i]]] := 1;
  for i := 1 to cv do g[i, i] := 0;
end;
 
var leaf: array [1..100] of boolean;
 
procedure GetTree (a: integer);
var i: integer;
begin
  v[a] := 1;
  leaf [a] := true;
  for i := 1 to cv do
  if g [a, i] > 0 then
    if v[i] = 0 then
    begin
      leaf [a] := false;
      GetTree (i);
    end;
end;
 
procedure CheckIfLeaf (a: integer);
var j, i: integer;
begin
  j := 0;
  for i := 1 to cv do
    if g[a,i] > 0 then inc (j);
  if j = 1 then leaf[a] := true else Leaf[a] := false;
end;
 
procedure CountLeafs;
var i: integer;
begin
  Leafs := 0;
  for i := 1 to cv do
    if leaf[i] then inc (Leafs);
end;
 
procedure ProcessSimpleTree;
var i, j: integer;
begin
  i := 1; while leaf[i] = false do inc (i);
  j := i+1; while leaf[j] = false do inc (j);
  inc (rc);
  rb[rc] := i;
  re[rc] := j;
end;
 
procedure MoveFromLeaf (var a: integer);
var i: integer;
begin
  for i := 1 to cv do
  if g[a, i] > 0 then
    begin
      a := i;
      exit;
    end;
end;
 
procedure SubLeafsThere (a: integer; branch: byte);
var i: integer;
    j: integer;
begin
  j := 0;
  v[a] := 2;
  for i := 1 to cv do
  if (v[i] = 0) then
    if g[a, i] > 0 then
      begin
        SubLeafsThere (i, branch);
        j := 2;
      end;
 if leaf [a] then
     begin
       inc (LeafsThere [Branch]);
       bral[a] := Branch;
     end;
end;
 
procedure CalcLeafsThere (a: integer);
var i: integer;
begin
  for i := 1 to cv do v[i] := 0;
  for i := 1 to cv do LeafsThere[i] := 0;
  v[a] := 1;
  for i := 1 to n do
  if g[a, i] > 0 then if v[i] = 0 then
    SubLeafsThere (i, i);
end;
 
function ManyBranchesHaveLeafs: boolean;
var i, j: integer;
begin
  j := 0;
  for i := 1 to cv do
    if LeafsThere [i] > 0 then inc (j);
  if j > 1 then ManyBranchesHaveLeafs := true
           else ManyBranchesHaveLeafs := false;
end;
 
procedure FindTwoLeafs;
var max, max1: integer;
    bst, bst1: integer;
    i, j: integer;
begin
  max := 0; bst := 0;
  for i := 1 to cv do
    if LeafsThere [i] > max then
      begin max := LeafsThere[i]; bst := i; end;
  max1 := 0;
  bst1 := 0;
  for i := 1 to cv do
    if LeafsThere [i] > max1 then
      if i <> bst then
      begin max1 := LeafsThere[i]; bst1 := i; end;
 
  dec (LeafsThere [bst]);
  dec (LeafsThere [bst1]);
  dec (Leafs, 2);
  inc (rc);
  for i := 1 to cv do
    if leaf[i] and (Bral [i] = bst)  then
      begin leaf[i] := false; rb [rc] := i; break; end;
  for i := 1 to cv do
    if leaf[i] and (Bral [i] = bst1) then
      begin leaf[i] := false; re [rc] := i; break; end;
end;
 
procedure MoveToNextNode (var a: integer);
var i: integer;
begin
  for i := 1 to cv do
   if LeafsThere [i] > 0 then
      begin
        a := i;
        exit;
      end;
end;
 
procedure TreatLastLeaf;
var i,  l: integer;
begin
  for i := 1 to cv do
    if leaf [i] then begin l := i; break; end;
  for i := 1 to cv do
    if (v[i] > 0) and (g[l, i] = 0) then
      begin
        inc (rc);
        rb[rc] := l;
        re[rc] := i;
        exit;
      end;
end;
 
procedure PartProcessTree (a: integer);
var i: integer;
label BigTree;
begin
  for i := 1 to cv do leaf [i] := false;
  v[a] := 1;
  inc (Trees);
  TreeB [Trees] := rc + 1;
  for i := 1 to cv do
    if g [i, a] <> 0 then Goto BigTree;
  oneLeaf [Trees] := a;
  exit;
 
BigTree:
  GetTree (a);
  for i := 1 to cv do if Leaf[i] then
    begin
      OneLeaf [Trees] := i;
      exit;
    end;
end;
 
procedure ProcessTree (a: integer);
var i: integer;
label BigTree;
begin
  for i := 1 to cv do leaf [i] := false;
  v[a] := 1;
  inc (Trees);
  TreeB [Trees] := rc + 1;
  for i := 1 to cv do
    if g [i, a] <> 0 then Goto BigTree;
  inc (rc);
  rb[rc] := a;
  re[rc] := a;
  exit;
 
BigTree:
  GetTree (a);
  CheckIfLeaf (a);
  CountLeafs;
  if Leafs = 2 then
      begin
        ProcessSimpleTree;
        exit;
      end;
 
  if leaf [a] then MoveFromLeaf (a);
  CalcLeafsThere (a);
  repeat
    while ManyBranchesHaveLeafs do
      FindTwoLeafs;
    if (Leafs >= 2) then
        begin
          MoveToNextNode (a);
          CalcLeafsThere (a);
        end;
  until Leafs < 2;
  if Leafs = 1 then TreatLastLeaf;
end;
 
procedure TaskD;
var savrb, rcc, i: integer;
begin
  BuildGraph;
  KillBad;
  for i := 1 to n do
    v[i] := 0;
  cv := 0;
  for i := 1 to n do
  if v[i] = 0 then
  begin
    inc (cv); Fill (i, cv);
    ref [cv] := i;
  end;
  vv := v;
  BuildCondensedGraph;
rc := 0;
  repeat
      for i := 1 to cv do
        v[i] := 0;
      Trees := 0; TreeB [0] := 0;
      for i := 1 to cv do
        if v[i] = 0 then PartProcessTree (i);
      if trees > 1 then
         begin
           inc (rc);
           rb[rc] := OneLeaf[1];
           re[rc] := OneLeaf[2];
           g[OneLeaf[1], OneLeaf[2]] := 1;
           g[OneLeaf[2], OneLeaf[1]] := 1;
         end;
    until trees <= 1;
 
  for i := 1 to cv do
    v[i] := 0;
  Trees := 0; TreeB [0] := 0;
  for i := 1 to cv do
    if v[i] = 0 then ProcessTree (i);
  if trees > 1 then
      begin
        SavRb := rb[Treeb[1]];
        for i := 1 to Trees-1 do
        begin
          rb [Treeb[i]] := re [Treeb[i]];
          re [Treeb[i]] := rb [Treeb[i+1]];
        end;
        rb [Treeb[Trees]] := re[Treeb[Trees]];
        re [Treeb[Trees]] := Savrb;
      end;
  rcc := rc;
  for i := 1 to rc do if rb[i] = re[i] then dec (rcc);
  writeln (rcc);
  for i := 1 to rc do
    if rb[i] <> re [i] then writeln (ref[rb[i]], ' ', ref[re[i]]);
end;
 
procedure Solve;
begin
  assign (output, 'orientgr.out'); rewrite (output);
  PrepareLinks;
  TaskA;
  TaskB;
  TaskV;
  TaskG;
  TaskD;
  close (output);
end;
 
begin
  times := time;
  new (badr);
  ReadAll;
  Solve;
end.

 


© Особенности национальных задач по информатике