[置顶] 图论总结

图存储

邻接链表-前向星

 procedure init(a,b,a:longint);
 begin
  w[len,1]:=b; w[len,2]:=c;
  if w[a,3]=0
  then w[a,3]:=len else w[w[a,1],3]:=len;
  w[a,1]:=len; inc(len);
 end;

 readln(n,m); len:=n+1; //n:点数 m:边数
 for i:=1 to m do
  begin
   readln(a,b,c); //a,b:两点 c:边权
   init(a,b,c); init(b,a,c);
  end;

其他存法

  • 受到尼克的任务的启发我们可以这样来存

  • 如果我们能让某个点到其他点的距离的存储是连续的话我们就可以用for循环来判断了

  • (其实这一切的开始是因为我写while循环经常超时QAQAQAQ)

for i:=1 to n do
 readln(x[i,1],x[i,2]); //x[i,1]与x[i,2]能连通,但这里我们暂且认为这是一个有向图,即x[i,1]通向x[i,2]
sort(1,n); //对x[i,1]排序
for i:=1 to n do
 if w[x[i,1],1]=0
 then 
  begin
   w[x[i,1],1]:=1;
   w[x[i,1],2]:=x[i,1];
   w[x[i,1],3]:=x[i,1];
  end
 else inc(w[i,3]);

w[i,1]表示有没有出度
x[w[i,2]~w[i,3],2]是x[w[i,1],1]的点能到达的点

最短路

Dijkstra

时间复杂度 O(N2)

var
 n,m,start,finish:longint;
 i,j:longint;
 min,t:longint;
 a,b,c:longint;
 w:array[0..1000,0..1000]of longint;
 dist,s:array[0..1000]of longint;
begin
 readln(m,n); finish:=1; start:=n; //m条边 n个点 finish为终点 start为起点
 for i:=1 to m do  
  begin
   readln(a,b,c);
   if ((w[a,b]<>0)and(c<w[a,b]))or(w[a,b]=0) //判重!!!
   then
    begin
     w[a,b]:=c;
     w[b,a]:=c;
    end;
  end;
 for i:=1 to n do
  dist[i]:=maxlongint;
 dist[start]:=0;
 while s[0]<>n do
  begin
   min:=maxlongint;
   for i:=1 to n do
    if (dist[i]<min)and(s[i]=0)
    then begin min:=dist[i]; t:=i; end;
   for i:=1 to n do
    if (s[i]=0)and(w[t,i]<>0)and(dist[t]+w[t,i]<dist[i])
    then dist[i]:=dist[t]+w[t,i];
   s[t]:=1; inc(s[0]);
   if s[finish]=1 then break;
  end;
 writeln(dist[finish]);
end.

SPFA

多组数据的话,先对w数组初始化
时间复杂度:平均复杂度 O(2m)
最坏情况下复杂度退化为O(N^2)好吧,这个有待考证,但是可以确定的是网格图一定会卡SPFA

var
 w:array[0..5010,1..3]of longint;
 s,dist:array[0..1010]of longint;
 t:array[0..8010]of longint;
 i,j,k:longint;
 m,n,len,a,b,c,head,tail,start,finish,tt,v:longint;
procedure init(a,b,c:longint);
begin
 w[len,1]:=b; w[len,2]:=c;
 if w[a,3]=0
 then w[a,3]:=len else w[w[a,1],3]:=len;
 w[a,1]:=len; inc(len);
end;

begin
 readln(m,n); len:=n+1;
 for i:=1 to m do
  begin
   readln(a,b,c);
   init(a,b,c); init(b,a,c);
  end;
 start:=n; finish:=1;
 for i:=1 to n do
  dist[i]:=maxlongint;  ////应赋值为maxL*m,而不是maxL
 head:=1; tail:=2; t[head]:=start; s[start]:=1; dist[start]:=0;
 while head<tail do
  begin
   v:=t[head]; tt:=w[v,3];
   while tt<>0 do
    begin
     if w[tt,2]+dist[v]<dist[w[tt,1]]
     then begin
      dist[w[tt,1]]:=w[tt,2]+dist[v];
      if s[w[tt,1]]=0
      then begin s[w[tt,1]]:=1; t[tail]:=w[tt,1]; inc(tail); end;
     end;
      tt:=w[tt,3];
    end;
   s[v]:=0; inc(head);
  end;
 writeln(dist[finish]);
end.

差分约束系统

  • 若干个形如 xixj<=a[k] 组成的方程组,可以看成点 j 到点 i 的距离为 a[k] ,然后我们加一个源,该点指向其他各个点,权值为0
  • 建完图后,我们求出源点到其它点的最短路径,即为答案
  • 我们可以发现 xixj>=a[k] 这个式子可以转换成 xjxi<=a[k] ,可以看成点 i 到点 j 的边权值为 a[k] ,也就是边的指向相反,权值变负
  • 两种建图方法答案一定不同
  • 我们回到开始的时候,该方程组为不定方程组,所以有无数组整数解或无解,所以根据所需调整解

Floyd

时间复杂度 O(N3)

var
 t,w:array[0..100,0..100]of longint;
 i,j,k:longint;
 n,q,a,b:longint;
begin
 readln(n);
 for i:=1 to n do
  begin
   for j:=1 to n do
    read(w[i,j]);
   readln;
  end;
 readln(q);
 for k:=1 to n do
  for i:=1 to n do
   for j:=1 to n do
    if (w[i,k]+w[k,j]<w[i,j])and(i<>j)
    then w[i,j]:=w[i,k]+w[k,j];
 for i:=1 to q do
  begin
   readln(a,b);
   writeln(w[a,b]);
  end;
end.

二分图

二分图判断

  • 二分图性质:没有奇圈(每个环的边都为偶数条)
  • 根据这个性质我们用染色法来判断,对于一个点我们把它染成1,那么就把它所连接的点的颜色染成0(如果是0就染成1),若有矛盾则不是二分图

DFS

procedure dfs(a:longint);
var tt,c:longint;
begin
 if x[a]=1 then c:=0 else c:=1;
 tt:=w[a,3];
 while tt<>0 do
  begin
   if x[w[tt,1]]=-1
   then begin x[w[tt,1]]:=c; dfs(w[tt,1],b); end
   else if x[w[tt,1]]<>c then k:=0;
   tt:=w[tt,3];
  end;
end;

function check():longint;
begin
 k:=1;
 for i:=1 to n do
  x[i]:=-1;
 for i:=1 to n do
  begin
   if x[i]=-1
   then begin x[i]:=1; dfs(i); end;
   if k=0 then break;
  end;
 exit(k);
end;

BFS

procedure bfs(a:longint);
var v,tt,c:longint;
begin
 t[1]:=a; head:=1; tail:=2;
 while head<tail do
  begin
   v:=t[head]; inc(head); tt:=w[v,3]; if x[v]=0 then c:=1 else c:=0;
   while tt<>0 do
    begin
     if x[w[tt,1]]=-1
     then begin x[w[tt,1]]:=c; t[tail]:=w[tt,1]; inc(tail); end
     else if x[w[tt,1]]<>c then k:=0;
     tt:=w[tt,3];
    end;
  end;
end;

function check():longint;
begin
 k:=1;
 for i:=1 to n do
  x[i]:=-1;
 for i:=1 to n do
  begin
   if x[i]=-1
   then begin x[i]:=1; bfs(i); end;
   if k=0 then break;
  end;
 exit(k);
end;

二分图匹配

移步我的另一篇二分图匹配

图联通

先说几个定义吧

  • 回边:
  • 交叉边

有向图强连通分量

Tarjan

var
 w:array[0..70000,1..2]of longint;
 low,dfn,p,t:array[0..10005]of longint;
 i,j,k:longint;
 n,m,len,a,b,tmp,ans:longint;
procedure init(a,b:longint);
begin
 w[len,1]:=b;
 if w[a,2]=0
 then w[a,2]:=len else w[w[a,1],2]:=len;
 w[a,1]:=len; inc(len);
end;

function min(a,b:longint):longint;
begin
 if a>b then exit(b) else exit(a);
end;

procedure tarjan(a:longint);
var tt,s:longint;
begin
 dfn[a]:=tmp; low[a]:=tmp; inc(tmp);
 inc(t[0]); t[t[0]]:=a; p[a]:=1;
 tt:=w[a,2];
 while tt<>0 do
  begin
   if dfn[w[tt,1]]=0
   then begin tarjan(w[tt,1]); low[a]:=min(low[a],low[w[tt,1]]); end
   else if p[w[tt,1]]=1 then low[a]:=min(low[a],dfn[w[tt,1]]);
   tt:=w[tt,2];
  end;
 s:=t[0];
 if dfn[a]=low[a] then begin
  repeat
   p[t[t[0]]]:=0;
   write(t[t[0]],' ');
   dec(t[0]);
  until a=t[t[0]+1];
  writeln;
 end;
end;

begin
 readln(n,m); len:=n+1;
 for i:=1 to m do
  begin readln(a,b); init(a,b); end;
 tmp:=1;
 for i:=1 to n do
  if dfn[i]=0 then tarjan(i);
end.

无向图双连通分量

割点

割边

树相关

生成树

Prim

时间复杂度 O(N2)

var
 w:array[1..100,1..100]of longint;
 lowcost,nearvex:array[0..100]of longint;
 i,j,k:longint;
 n,m,ans,min,tt,kk:longint;
 a,b,c,x,y,z:longint;
begin
 readln(n,m);  //n:点数,m:边数
 for i:=1 to m do
  begin
   for j:=1 to n do
    read(w[i,j]);
   readln;
  end;
 {for i:=1 to n do
  begin
   readln(a,b,c);
   w[a,b]:=c; w[b,a]:=c;
  end;}
 for i:=1 to n do
  lowcost[i]:=maxlongint;
  inc(nearvex[0]); nearvex[1]:=-1;
  lowcost[1]:=0; kk:=1;
 while nearvex[0]<>n do
  begin
   for i:=1 to n do
    if (w[kk,i]<>0)and(w[kk,i]<lowcost[i])
    then lowcost[i]:=w[kk,i];
   min:=maxlongint;
   for i:=1 to n do
    if (nearvex[i]<>-1)and(lowcost[i]<min)
    then begin min:=lowcost[i]; tt:=i; end;
   inc(ans,min); nearvex[tt]:=-1; kk:=tt; inc(nearvex[0]);
  end;
 writeln(ans);
end.

Kruskal

时间复杂度 O(mlogm2)

var
 rank,father:array[0..100]of longint;
 edge:array[0..4950,1..3]of longint;
 i,j,k:longint;
 n,e,ans:longint;
 a,b,c:longint;
procedure sort(l,r: longint);
var i,j,k,x,y: longint;
begin
 i:=l; j:=r; x:=edge[(l+r) div 2,3];
 repeat
  while edge[i,3]<x do inc(i);
  while x<edge[j,3] do dec(j);
  if not(i>j)
  then
   begin
    for k:=1 to 3 do
     begin y:=edge[i,k]; edge[i,k]:=edge[j,k]; edge[j,k]:=y; end;
    inc(i); dec(j);
   end;
 until i>j;
 if l<j then sort(l,j);
 if i<r then sort(i,r);
end;

function getfather(a:longint):longint;
begin
 if father[a]=a
 then exit(a);
 father[a]:=getfather(father[a]);
 exit(father[a]);
end;

procedure union(a,b:longint);
var x,y,tt:longint;
begin
 x:=getfather(a);
 y:=getfather(b);
 tt:=rank[x]+rank[y];
 if rank[x]>rank[y]
 then begin rank[x]:=tt; rank[y]:=rank[x]; father[y]:=x; getfather(b); end
 else begin rank[y]:=tt; rank[x]:=rank[y]; father[x]:=y; getfather(a); end;
end;

begin
 readln(n,e);
 for i:=1 to e do
  begin
   readln(a,b,c);
   inc(edge[0,1]);
   edge[edge[0,1],1]:=a;
   edge[edge[0,1],2]:=b;
   edge[edge[0,1],3]:=c;
  end;
 sort(1,n);
 for i:=1 to n do
  begin
   father[i]:=i;
   rank[i]:=1;
  end;
 for i:=1 to e do
  begin
   if getfather(edge[i,1])<>getfather(edge[i,2])
   then begin union(edge[i,1],edge[i,2]); inc(ans,edge[i,3]); end;
  end;
 writeln(ans);
end.

LCA

Tarjan

var
 w:array[0..500005,1..2]of longint;
 g:array[0..500005,1..3]of longint;
 vis,ans,fa:array[0..250005]of longint;
 i,j,k:longint;
 n,m,root,len,a,b:longint;
procedure initw(a,b:longint);
begin
 w[len,1]:=b;
 if w[a,2]=0
 then w[a,2]:=len else w[w[a,1],2]:=len;
 w[a,1]:=len; inc(len);
end;

procedure initg(a,b,c:longint);
begin
 g[len,1]:=b; g[len,2]:=c;
 if g[a,3]=0
 then g[a,3]:=len else g[g[a,1],3]:=len;
 g[a,1]:=len; inc(len);
end;

function get(a:longint):longint;
begin
 if fa[a]=a then exit(a);
 fa[a]:=get(fa[a]);
 exit(fa[a]);
end;

procedure tarjan(a:longint);
var tt:longint;
begin
 vis[a]:=1; tt:=g[a,3];
 while tt<>0 do
  begin
   if vis[g[tt,1]]=1
   then ans[g[tt,2]]:=get(g[tt,1]);
   tt:=g[tt,3];
  end;
 tt:=w[a,2];
 while tt<>0 do
  begin
   if vis[w[tt,1]]=0
   then begin
    tarjan(w[tt,1]);
    fa[w[tt,1]]:=a;
   end;
   tt:=w[tt,2];
  end;
end;

begin
 readln(n); len:=n+1;
 for i:=1 to n do
  begin
   readln(a);
   if a=0
   then root:=i
   else initw(a,i);
  end;
 readln(m); len:=n+1;
 for i:=1 to m do
  begin
   readln(a,b);
   initg(a,b,i); initg(b,a,i);
  end;
 for i:=1 to n do
  fa[i]:=i;
 fillchar(vis,sizeof(vis),0);
 tarjan(root);
 for i:=1 to m do
  writeln(ans[i]);
end.

倍增法

var
 st:array[0..250005,0..30]of longint;
 w:array[0..500005,1..2]of longint;
 t,dep:array[0..250005]of longint;
 i,j,k:longint;
 n,m,len,root,a,b,head,tail,v,tt:longint;
procedure init(a,b:longint);
begin
 w[len,1]:=b;
 if w[a,2]=0
 then w[a,2]:=len else w[w[a,1],2]:=len;
 w[a,1]:=len; inc(len);
end;

function lca(a,b:longint):longint;
var i:longint;
begin
 if dep[a]>dep[b] then begin k:=a; a:=b; b:=k; end;
 for i:=0 to trunc(ln(n)/ln(2)) do
  if (dep[b]-dep[a])>>i and 1=1
  then b:=st[b,i];
 if b=a then exit(a);
 for i:=trunc(ln(dep[b])/ln(2))+1 downto 0 do
  if st[a,i]<>st[b,i]
  then begin a:=st[a,i]; b:=st[b,i]; end;
 exit(st[a,0]);
end;

begin
 readln(n); len:=n+1;
 for i:=1 to n do
  begin
   readln(st[i,0]);
   if st[i,0]=0
   then root:=i else init(st[i,0],i);
  end;
 dep[root]:=1; head:=1; tail:=2; t[1]:=root;
 while head<tail do
  begin
   v:=t[head]; inc(head); tt:=w[v,2];
   while tt<>0 do
    begin
     dep[w[tt,1]]:=dep[v]+1; t[tail]:=w[tt,1]; inc(tail);
     tt:=w[tt,2];
    end;
  end;
 for j:=1 to trunc(ln(n)/ln(2)) do
  for i:=1 to n do
   st[i,j]:=st[st[i,j-1],j-1];
 readln(m);
 for i:=1 to m do
  begin
   readln(a,b);
   writeln(lca(a,b));
  end;
end.

矩阵树定理

prufer编码

树的重心

树的直径

两次DFS
1.第一次随便找一个点 a 开始DFS处理出从这个点到其他点的距离
2.第二次从距离最大值的点重新DFS一遍,最大距离即为直径
然后我们来证一下
若第一次找到的点 a ,分为两种情况1. a 在树的直径上2. a 不在树的直径上

  • a 在树的直径上:显然第一次找到的点为树的直径中其中一个点,第二次就更显然了
  • a 不在树的直径上:
    • 第一次找到的点在直径上:也显然会找到一个直径端点
    • 第一次找到的点不在直径上:第一次从 u 找到点 v ,然后就有 dis(U,V)>=dis(U,X)+dis(X,S) ,那么显然 dis(U,V)+dis(U,X)>=dis(S,X) +dis(X,T) 得到 dis(U,V)+dis(T,X)+dis(U,X)>=dis(S,X)+dis(X,T) (S,T) 为直径矛盾
      [置顶] 图论总结_第1张图片
      http://poj.org/problem?id=2631
const
    maxn=10005;
var
    w:array[0..3*maxn,1..3]of longint;
    dist:array[0..maxn]of longint;
    i,j,k:longint;
    n,m,len,a,b,c:longint;
procedure init(a,b,c:longint);
begin
    w[len,1]:=b; w[len,2]:=c;
    if w[a,3]=0 then w[a,3]:=len else w[w[a,1],3]:=len;
    w[a,1]:=len; inc(len);
end;

procedure dfs(a,b:longint);
var tt:longint;
begin
    tt:=w[a,3]; 
    while tt<>0 do
        begin
            if w[tt,1]<>b then begin dist[w[tt,1]]:=dist[a]+w[tt,2]; dfs(w[tt,1],a); end;
            tt:=w[tt,3];
        end;
end;

begin
    len:=maxn; n:=1; 
    while not eof do
        begin
            readln(a,b,c); inc(n);
            init(a,b,c); init(b,a,c);
        end;
    fillchar(dist,sizeof(dist),0);
    dfs(1,0); a:=0; dist[a]:=0;
    for i:=1 to n do
        if dist[i]>dist[a] then a:=i;
    fillchar(dist,sizeof(dist),0);
    dfs(a,0); a:=0; dist[a]:=0;
    for i:=1 to n do
        if dist[i]>dist[a] then a:=i;
    writeln(dist[a]);   
end.

你可能感兴趣的:([置顶] 图论总结)