一、图的定义
图(Graph)是由两个集合构成,一个是非空但是有限的顶点集合V,另一个是描述集合间的关系边的集合E。
因此图可以表示为G=(V,E).每条边是一对顶点(v,w)且v,w∈V。
通常|V|和|E|表示顶点个数和边的数量。图中顶点一定不能为空,而边可以为空。
二、图的相关术语
无向图,有向图
邻接点:如果<v.w>是无向图中的任意一条边。那么v和w互为邻接点,如果<v,w>是有向图的任意一条边,那么称v邻接到w
路径,简单路,回路,无环路:
图中一条路径Path是指一顶点序列:v1,v2....vn。序列中任何相邻的两顶点都可以在图中找到对应的边.一条路径长度是这条路径所包含的边数。
一条简单路径是指除了路径的首位顶点之外,其他顶点都是不同的。有向图的一条回路也称一个环是指v1=vn的一条路径。如果一个有向图中不存在回路,那么这图称为无环图。对于无向图而言,构成回路最少顶点数为3.
顶点的入度与出度:顶点v的度(degree)是指依附于该点的边数。在有向图中顶点分为入度和出度。顶点v的入度(In-degree)是指以顶点v为终点的边的数目。顶点v的出度(out-degree)是指以顶点v为起点的边的数目。
权和网络:在边上附上一些数据信息。通常称这个信息为权(weight)或者代价(cost).边上带权的图称为网图或者网络。
子图:对于图G=(V,E)和图G1=(V1,E1)。若V1是V的子集,E1是E的子集,则称G1是G的子图
生成树与生成森林:所谓连通图G的生成树是G的包含其中n个顶点的极小连通子图。它必定包含了n-1条边,生成树不是唯一的。当图G是一颗树当且仅当G满足下列4条件之一:
G有n-1条边,且没有环。
G有n-1条边,且G是连通的。
G中的每一个顶点有且只有一条路径相连。
G是连通的,但是删除任何一条边就会使得它不连通。
生成森林:非连通图中由于每一个连通分量都是一个极小的连通子图。即一颗生成树可以对应一个连通分量。所有的这些连通分量的生成树就构成了森林。
三、图的操作
1.储存
1 #include<iosrteam> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 using namespace std; 9 namespace Moxing{ 10 const int N=20005,M=100005; 11 int n,m,last[N],cnt; 12 struct node{ 13 int to,dis,nxt; 14 }edge[M<<1]; 15 void add(int from,int to,int dis){ 16 edge[++cnt].to=to,edge[cnt].dis=dis,edge[cnt].nxt=last[from],last[from]=cnt; 17 } 18 struct main{ 19 main(){ 20 n=read();int x,y,z; 21 for(int i=1;i<=m;i++){ 22 x=read(),y=read(),z=read(); 23 add(x,y,z),add(y,x,z); 24 } 25 exit(0); 26 } 27 int read(){ 28 int x=0,f=1; 29 char ch=getchar(); 30 while(!isdigit(ch)){ 31 if(ch=='-') f=-1; 32 ch=getchar(); 33 } 34 while(isdigit(ch)){ 35 x=x*10+ch-'0',ch=getchar(); 36 } 37 return x*f; 38 } 39 }UniversalLove; 40 } 41 int main(){ 42 Moxing::main(); 43 }
2.遍历
1 void dfs(int x){ 2 vis[x]=1; 3 for(int i=last[x];i;i=edge[i].nxt){ 4 int y=edge[i].to; 5 if(vis[y]) continue ; 6 dfs(y); 7 } 8 }
1 void bfs(){ 2 fill(deep+1,deep+1+n,0);deep[1]=1; 3 queue<int>q; 4 q.push(1); 5 while(!q.empty()){ 6 int x=q.front(),q.pop(); 7 for(int i=last[x];i;i=edge[i].nxt){ 8 int y=edge[i].to; 9 if(deep[y]) continue ; 10 deep[y]=deep[x]+1; 11 q.push(y); 12 } 13 } 14 }
三、树
1.树链剖分
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e5+5; 4 int n,m,root,mod,a[N],last[N<<1],cnt; 5 struct node{ 6 int to,nxt; 7 }edge[N<<1]; 8 void add(int from,int to){ 9 edge[++cnt].to=to;edge[cnt].nxt=last[from];last[from]=cnt; 10 } 11 int fa[N],size[N],deep[N],son[N]; 12 void dfs1(int x){ 13 size[x]=1; 14 for(int i=last[x];i;i=edge[i].nxt){ 15 int y=edge[i].to; 16 if(y!=fa[x]){ 17 fa[y]=x;deep[y]=deep[x]+1; 18 dfs1(y); 19 size[x]+=size[y]; 20 if(size[son[x]]<size[y]) son[x]=y; 21 } 22 } 23 } 24 int dfn[N],top[N],tot,b[N]; 25 void dfs2(int x,int tp){ 26 dfn[x]=++tot;b[tot]=a[x];//point weight:rk[tot]=x; 27 top[x]=tp; 28 if(son[x]) dfs2(son[x],tp); 29 for(int i=last[x];i;i=edge[i].nxt){ 30 int y=edge[i].to; 31 if(y!=son[x]&&y!=fa[x]) dfs2(y,y); 32 } 33 34 } 35 struct nod{ 36 int l,r,sum,lazy; 37 }tree[N<<2]; 38 39 int interlen(int tr){ 40 return tree[tr].r-tree[tr].l+1; 41 } 42 void pushup(int tr){ 43 tree[tr].sum=(tree[tr<<1].sum+tree[tr<<1|1].sum+mod)%mod; 44 } 45 46 void pushdown(int tr){ 47 int lazy=tree[tr].lazy; 48 if(!lazy) return ; 49 tree[tr<<1].sum=(tree[tr<<1].sum+interlen(tr<<1)*lazy)%mod; 50 tree[tr<<1|1].sum=(tree[tr<<1|1].sum+interlen(tr<<1|1)*lazy)%mod; 51 tree[tr<<1].lazy=(tree[tr<<1].lazy+lazy)%mod; 52 tree[tr<<1|1].lazy=(tree[tr<<1|1].lazy+lazy)%mod; 53 tree[tr].lazy=0; 54 } 55 void build(int tr,int l,int r){ 56 tree[tr].l=l;tree[tr].r=r; 57 if(l==r){ 58 tree[tr].sum=b[l]; 59 return ; 60 } 61 int mid=(l+r)>>1; 62 build(tr<<1,l,mid);build(tr<<1|1,mid+1,r); 63 pushup(tr); 64 } 65 void change(int tr,int l,int r,int d){ 66 67 if(tree[tr].l>=l&&tree[tr].r<=r){ 68 tree[tr].lazy=(tree[tr].lazy+d)%mod; 69 tree[tr].sum=(tree[tr].sum+interlen(tr)*d)%mod;//puts("Moxingtianxia"); 70 return ; 71 } 72 pushdown(tr); 73 int mid=(tree[tr].l+tree[tr].r)>>1; 74 if(l<=mid) change(tr<<1,l,r,d); 75 if(r>mid) change(tr<<1|1,l,r,d); 76 pushup(tr); 77 } 78 int query(int tr,int l,int r){ 79 int ans=0; 80 if(tree[tr].l>=l&&tree[tr].r<=r){ 81 //ans+=tree[tr].sum; 82 return tree[tr].sum;//ans; 83 } 84 pushdown(tr); 85 int mid=(tree[tr].l+tree[tr].r)>>1; 86 if(l<=mid) ans=(ans+query(tr<<1,l,r))%mod; 87 if(r>mid) ans=(ans+query(tr<<1|1,l,r))%mod; 88 return ans; 89 } 90 void treesum(int x,int y){ 91 int ans=0; 92 while(top[x]!=top[y]){ 93 if(deep[top[x]]<deep[top[y]]) swap(x,y); 94 ans=(ans+query(1,dfn[top[x]],dfn[x]))%mod; 95 x=fa[top[x]]; 96 } 97 if(deep[x]>deep[y]) swap(x,y); 98 ans=(ans+query(1,dfn[x],dfn[y]))%mod; 99 printf("%d\n",ans); 100 } 101 void treeadd(int x,int y,int d){ 102 while(top[x]!=top[y]){ 103 if(deep[top[x]]<deep[top[y]]) swap(x,y); 104 change(1,dfn[top[x]],dfn[x],d); 105 x=fa[top[x]]; 106 } 107 if(deep[x]>deep[y]) swap(x,y); 108 change(1,dfn[x],dfn[y],d); 109 return ; 110 } 111 int main(){ 112 scanf("%d%d%d%d",&n,&m,&root,&mod); 113 for(int i=1;i<=n;i++){ 114 scanf("%d",&a[i]);a[i]%=mod; 115 } 116 for(int i=1;i<=n-1;i++){ 117 int x,y; 118 scanf("%d%d",&x,&y); 119 add(x,y);add(y,x); 120 } 121 deep[root]=1;// 122 dfs1(root); 123 dfs2(root,root); 124 build(1,1,n); 125 while(m--){ 126 int opt,x,y,z; 127 scanf("%d",&opt); 128 switch(opt){ 129 case 1:{ 130 scanf("%d%d%d",&x,&y,&z); 131 treeadd(x,y,z%mod); 132 break; 133 } 134 case 2:{ 135 scanf("%d%d",&x,&y); 136 treesum(x,y); 137 break; 138 } 139 case 3:{ 140 scanf("%d%d",&x,&y); 141 change(1,dfn[x],dfn[x]+size[x]-1,y%mod); 142 break; 143 } 144 case 4:{ 145 scanf("%d",&x); 146 printf("%d\n",query(1,dfn[x],dfn[x]+size[x]-1)); 147 break; 148 } 149 } 150 } 151 }
2.树的直径
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 using namespace std; 9 namespace Moxing { 10 //树的直径的定义:树上最长路径 11 const int N=1e5+5; 12 int last[N],cnt; 13 struct node { 14 int to,nxt,dis; 15 } edge[N<<1]; 16 void add(int from,int to,int dis) { 17 edge[++cnt].to=to; 18 edge[cnt].dis=dis; 19 edge[cnt].nxt=last[from]; 20 last[from]=cnt; 21 } 22 int p,q,fa[N],d[N],ans; 23 bool vis[N]; 24 //d[x]:从x节点出发走向以X为根的子树 能够到达的最远节点距离 25 //d[x]=max(d[x],d[y]+edge[i].dis); 26 void dp(int x) { 27 vis[x]=1; 28 for(int i=last[x]; i; i=edge[i].nxt) { 29 int y=edge[i].to; 30 if(vis[y]) continue ; 31 dp(y); 32 ans=max(ans,d[x]+d[y]+edge[i].dis); 33 d[x]=max(d[x],d[y]+edge[i].dis); 34 } 35 } 36 //从任意一个结点出发,求出与出发点距离最远的节点记为p 37 //从结点p出发,再进行一次遍历,求出与p距离最远的节点记为q 38 void dfs(int x) { 39 for(int i=last[x]; i; i=edge[i].nxt) { 40 int y=edge[i].to; 41 if(fa[x]==y) continue; //不走回头路,也可以递归父亲结点省去fa数组空间 42 fa[y]=x; 43 d[y]=d[x]+edge[i].dis; //当前结点最长路径 44 dfs(y); 45 } 46 } 47 struct main { 48 main() { 49 int n; 50 scanf("%d",&n); 51 for(int i=1; i<=n; i++) fa[i]=i; 52 for(int i=1; i<=n-1; i++) { 53 int x,y,cost; 54 scanf("%d%d%d",&x,&y,&cost); 55 add(x,y,cost); 56 add(y,x,cost); 57 } 58 dfs(1); 59 int ansmax=0,ansindix=0; 60 for(int i=1; i<=n; i++) { 61 if(d[i]>ansmax) ansmax=d[i],ansindix=i; 62 d[i]=0; 63 fa[i]=i; 64 } 65 dfs(ansindix); 66 for(int i=1; i<=n; i++) { 67 cout<<d[i]<<' '; 68 } 69 cout<<endl; 70 //d[x]:从x节点出发走向以X为根的子树 能够到达的最远节点距离 71 //d[x]=max(d[x],d[y]+edge[i].dis); 72 memset(d,0,sizeof(d)); 73 dp(1); 74 for(int i=1; i<=n; i++) { 75 cout<<d[i]<<' '; 76 } 77 cout<<endl<<ans; 78 exit(0); 79 } 80 } UniversalLove; 81 } 82 int main() { 83 Moxing::main(); 84 }
3.树的重心
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 using namespace std; 9 namespace Moxing { 10 //定义:若以点u为根的有根树的所有子树(不包括整棵树)大小都不超过总节点数的一半,则称点u为重心. 11 //性质: 12 //1.重心一定存在。 13 //2.树中所有点到重心的距离和最小。 14 //3.树添加或者删除一个叶子,重心最多移动一条边的距离。 15 //4.将两棵树通过一条边连接成一棵新树,重心一定在连接两棵树重心的路径上。 16 const int N=1e5+5; 17 int last[N<<1],cnt; 18 struct node { 19 int to,nxt,dis; 20 } edge[N<<1]; 21 void add(int from,int to,int dis) { 22 edge[++cnt].to=to; 23 edge[cnt].dis=dis; 24 edge[cnt].nxt=last[from]; 25 last[from]=cnt; 26 } 27 int size[N],mx[N],rt=1,n; 28 bool vis[N]; 29 //mx[x]表示删去x节点后产生的子树中,最大的一棵的大小 30 void root(int x,int fa) { 31 size[x]=1; 32 mx[x]=0; 33 for(int i=last[x]; i; i=edge[i].nxt) { 34 int y=edge[i].to; 35 if(!vis[y]&&y!=fa) { 36 root(y,x); 37 size[x]+=size[y]; 38 mx[x]=max(mx[x],size[y]); 39 } 40 } 41 mx[x]=max(mx[x],n-size[x]); 42 if(mx[x]<mx[rt]) rt=x; 43 } 44 struct main { 45 main() { 46 scanf("%d",&n); 47 for(int i=1; i<=n-1; i++) { 48 int x,y,cost; 49 scanf("%d%d%d",&x,&y,&cost); 50 add(x,y,cost); 51 add(y,x,cost); 52 } 53 root(1,1); 54 cout<<rt; 55 exit(0); 56 } 57 } UniversalLove; 58 } 59 int main() { 60 Moxing::main(); 61 }
4.最近公共祖先
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 using namespace std; 9 namespace Moxing{ 10 //fa[x][k]: x的2^k辈祖先 fa[x][k]=fa[fa[x][k-1]][k-1] 11 const int N=5e5+5,M=20; 12 int last[N],cnt,n,m,s,t,deep[N]; 13 struct node { 14 int to,nxt; 15 } edge[N<<1]; 16 void add(int from,int to) { 17 edge[++cnt].to=to; 18 edge[cnt].nxt=last[from]; 19 last[from]=cnt; 20 } 21 struct nod{ 22 int fa; 23 }f[N][M]; 24 void dfs(int x){ 25 for(int i=last[x];i;i=edge[i].nxt){ 26 int y=edge[i].to; 27 if(deep[y]) continue ; 28 deep[y]=deep[x]+1; 29 f[y][0].fa=x; 30 dfs(y); 31 } 32 } 33 void cal(){ 34 for(int i=1;i<=t;i++){ 35 for(int j=1;j<=n;j++){ 36 f[j][i].fa=f[f[j][i-1].fa][i-1].fa; 37 } 38 } 39 } 40 int lca(int x,int y){ 41 if(deep[y]>deep[x]) swap(x,y); 42 for(int i=t;i>=0;i--){ 43 if(deep[f[x][i].fa]>=deep[y]) x=f[x][i].fa; 44 } 45 if(x==y) return x; 46 for(int i=t;i>=0;i--){ 47 if(f[x][i].fa!=f[y][i].fa){ 48 x=f[x][i].fa,y=f[y][i].fa; 49 } 50 } 51 return f[x][0].fa; 52 } 53 struct main{ 54 main(){ 55 scanf("%d%d%d",&n,&m,&s);t=log(n)/log(2)+1; 56 for(int i=1;i<=n-1;i++){ 57 int x,y; 58 scanf("%d%d",&x,&y); 59 add(x,y); 60 add(y,x); 61 } 62 deep[s]=1;f[s][0].fa=s; 63 dfs(s); 64 cal(); 65 while(m--){ 66 int x,y; 67 scanf("%d%d",&x,&y); 68 printf("%d\n",lca(x,y)); 69 } 70 exit(0); 71 } 72 }UniversalLove; 73 } 74 int main(){ 75 Moxing::main(); 76 }
5.最小生成树
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 using namespace std; 9 namespace Moxing{ 10 const int N=1e5+5; 11 struct node{ 12 int from,to,dis; 13 }E[N<<1]; 14 int n,m,fa[N],ans; 15 bool cmp(node a,node b){ 16 return a.dis<b.dis; 17 } 18 int get(int x){ 19 if(x==fa[x]) return x; 20 return fa[x]=get(fa[x]); 21 } 22 void kruskal(){ 23 for(int i=1;i<=n;i++){ 24 fa[i]=i; 25 } 26 sort(E+1,E+1+m,cmp); 27 for(int i=1;i<=m;i++){ 28 int x=get(E[i].from),y=get(E[i].to); 29 if(x==y) continue ; 30 fa[x]=y; 31 ans+=E[i].dis; 32 } 33 } 34 struct main{ 35 main(){ 36 scanf("%d%d",&n,&m); 37 for(int i=1;i<=m;i++){ 38 scanf("%d%d%d",&E[i].from,&E[i].to,&E[i].dis); 39 } 40 kruskal(); 41 printf("%d\n",ans); 42 exit(0); 43 } 44 }UniversalLove; 45 } 46 int main(){ 47 Moxing::main(); 48 }
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 using namespace std; 9 namespace Moxing { 10 int n,m,a[5005][5005],d[5005],ans; 11 bool vis[5005]; 12 void prim() { 13 // memset(vis,0,sizeof(vis)); 14 memset(d,0x3f,sizeof(d));d[1]=0; 15 for(register int k=2; k<=n; k++) { 16 int x=0; 17 for(register int i=1; i<=n; i++) { 18 if(!vis[i]&&(x==0||d[i]<d[x])) { 19 x=i; 20 } 21 } 22 vis[x]=1;//cout<<x<<' '; 23 for(register int i=1; i<=n; i++) { 24 if(!vis[i]) d[i]=min(d[i],a[x][i]); 25 //cout<<d[i]<<' '; 26 } 27 } 28 } 29 struct main{ 30 main(){ 31 scanf("%d%d",&n,&m); 32 memset(a,0x3f,sizeof(a)); 33 for(int i=1;i<=n;i++) a[i][i]=0; 34 for(int i=1;i<=m;i++){ 35 int x,y,z; 36 scanf("%d%d%d",&x,&y,&z); 37 a[y][x]=a[x][y]=min(a[x][y],z); 38 } 39 prim(); 40 for(int i=2;i<=n;i++) ans+=d[i]; 41 printf("%d\n",ans); 42 exit(0); 43 } 44 }UniversalLove; 45 } 46 int main(){ 47 Moxing::main(); 48 }
6.次小生成树
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 using namespace std; 9 namespace Moxing { 10 #define inf 2333333333333333; 11 const int M=2e5+5; 12 const int N=1e5+5; 13 int n,m,last[N],cnt,fa[N],deep[N],t; 14 bool pd[N]; 15 struct node { 16 int from,to,nxt; 17 long long dis; 18 } E[M],edge[M<<1]; 19 bool cmp(node a,node b) { 20 return a.dis<b.dis; 21 } 22 int get(int x) { 23 if(x==fa[x]) return x; 24 return fa[x]=get(fa[x]); 25 } 26 void add(int from,int to,long long dis) { 27 edge[++cnt].from=from; 28 edge[cnt].to=to; 29 edge[cnt].dis=dis; 30 edge[cnt].nxt=last[from]; 31 last[from]=cnt; 32 } 33 struct nod { 34 int fa; 35 long long maxx,minn; 36 } f[N][19]; 37 void dfs(int x,int fa) { 38 f[x][0].fa=fa; 39 for(int i=last[x]; i; i=edge[i].nxt) { 40 int y=edge[i].to; 41 if(y==fa) continue ; 42 f[y][0].fa=x; 43 f[y][0].maxx=edge[i].dis; 44 f[y][0].minn=-inf; 45 deep[y]=deep[x]+1; 46 dfs(y,x); 47 } 48 } 49 void cal() { 50 for(int i=1; i<=t; i++) { 51 for(int j=1; j<=n; j++) { 52 f[j][i].fa=f[f[j][i-1].fa][i-1].fa; 53 f[j][i].maxx=max(f[j][i-1].maxx,f[f[j][i-1].fa][i-1].maxx); 54 f[j][i].minn=max(f[j][i-1].minn,f[f[j][i-1].fa][i-1].minn); 55 if(f[j][i-1].maxx>f[f[j][i-1].fa][i-1].maxx) f[j][i].minn=max(f[j][i].minn,f[f[j][i-1].fa][i-1].maxx); 56 else if(f[j][i-1].maxx<f[f[j][i-1].fa][i-1].maxx) f[j][i].minn=max(f[j][i].minn,f[j][i-1].maxx); 57 } 58 } 59 } 60 int lca(int x,int y) { 61 if(deep[x]<deep[y]) swap(x,y); 62 for(int i=t; i>=0; i--) { 63 if(deep[f[x][i].fa]>=deep[y]) x=f[x][i].fa; 64 } 65 if(x==y) return x; 66 for(int i=t; i>=0; i--) { 67 if(f[x][i].fa!=f[y][i].fa) { 68 x=f[x][i].fa; 69 y=f[y][i].fa; 70 } 71 } 72 return f[x][0].fa; 73 } 74 long long query(int x,int y,long long dis) { 75 long long ans=-inf; 76 for(int i=t; i>=0; i--) { 77 if(deep[f[x][i].fa]>=deep[y]) { 78 if(dis!=f[x][i].maxx) ans=max(ans,f[x][i].maxx); 79 else ans=max(ans,f[x][i].minn); 80 x=f[x][i].fa; 81 } 82 } 83 return ans; 84 } 85 void kruskal() { 86 for(int i=1; i<=n; i++) { 87 fa[i]=i; 88 } 89 sort(E+1,E+1+m,cmp); 90 long long ans=0; 91 for(int i=1; i<=m; i++) { 92 int x=get(E[i].from),y=get(E[i].to),dis=E[i].dis; 93 if(x==y) continue ; 94 fa[x]=y; 95 ans+=dis; 96 pd[i]=1; 97 add(E[i].to,E[i].from,dis); 98 add(E[i].from,E[i].to,dis); 99 } 100 } 101 struct main { 102 main() { 103 scanf("%d%d",&n,&m); 104 t=log(n)/log(2)+1; 105 for(int i=1; i<=m; i++) { 106 scanf("%d%d%lld",&E[i].from,&E[i].to,&E[i].dis); 107 } 108 f[1][0].minn=-inf; 109 deep[1]=1; 110 dfs(1,-1); 111 cal(); 112 long long res=inf; 113 for(int i=1; i<=m; i++) { 114 if(!pd[i]) { 115 int x=E[i].from,y=E[i].to,anc=lca(x,y); 116 long long dis=E[i].dis; 117 long long maxx=query(x,anc,dis),maxy=query(y,anc,dis); 118 res=min(res,ans-max(maxx,maxy)+dis); 119 } 120 } 121 printf("%lld\n",res); 122 exit(0); 123 } 124 } UniversalLove; 125 } 126 int main() { 127 Moxing::main(); 128 }
kruskal 算法被证明,对于任何的u,v有u到v之间边权最大值小于等于u到v未选入的边的边权
不严格次小生成树:遍历每条未选的边(u,v,d),用它替换u和v之间的最大边
存次大值去掉未选入的边权中等于原边权的情况,只保留小于。
四、最短路
1.spfa
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 #include<queue> 9 using namespace std; 10 namespace Moxing { 11 const int N=1e5+5; 12 const int inf=2147483647; 13 int n,m,from,to,w,dis[N],last[N],vis[N],cnt,s; 14 struct node { 15 int to,dis,nxt; 16 } edge[N<<1]; 17 inline void add(int from,int to,int dis) { 18 edge[++cnt].to=to; 19 edge[cnt].dis=dis; 20 edge[cnt].nxt=last[from]; 21 last[from]=cnt; 22 } 23 queue<int>q; 24 void spfa(int x){ 25 fill(dis+1,dis+1+n,inf); 26 dis[x]=0,vis[x]=1; 27 q.push(x); 28 while(!q.empty()){ 29 int y=q.front();q.pop(); 30 vis[y]=0; 31 for(int i=last[y];i;i=edge[i].nxt){ 32 node z=edge[i]; 33 if(dis[z.to]>dis[y]+edge[i].dis){ 34 dis[z.to]=dis[y]+edge[i].dis; 35 if(vis[z.to]) continue ; 36 q.push(z.to),vis[z.to]=1; 37 } 38 } 39 } 40 } 41 struct main { 42 main() { 43 read(n),read(m),read(s); 44 for(register int i=1; i<=m; i++) { 45 read(from),read(to),read(w); 46 add(from,to,w); 47 } 48 spfa(s); 49 for(register int i=1; i<=n; i++) { 50 write(dis[i]),putchar(' '); 51 } 52 exit(0); 53 } 54 inline void read(int &x) { 55 x=0; 56 char ch=getchar(); 57 while (!isdigit(ch)) ch=getchar(); 58 while (isdigit(ch)) x=x*10+ch-'0',ch=getchar(); 59 return ; 60 } 61 inline void write(int x) { 62 if (x>9) write(x/10); 63 putchar(x%10+'0'); 64 return ; 65 } 66 } UniversalLove; 67 } 68 int main() { 69 Moxing::main(); 70 }
2.dijkstra
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 #include<queue> 9 using namespace std; 10 namespace Moxing { 11 const int N=1e5+5; 12 const int inf=2147483647; 13 int n,m,from,to,w,dis[N],last[N],vis[N],cnt,s; 14 struct node { 15 int to,dis,nxt; 16 } edge[N<<1]; 17 struct nde { 18 int to,dis; 19 bool friend operator < (nde a,nde b) { 20 return a.dis>b.dis; 21 } 22 } h,y; 23 inline void add(int from,int to,int dis) { 24 edge[++cnt].to=to; 25 edge[cnt].dis=dis; 26 edge[cnt].nxt=last[from]; 27 last[from]=cnt; 28 } 29 priority_queue<nde>q; 30 void dijkstra(int x) { 31 fill(dis+1,dis+1+n,inf); 32 dis[x]=0,h.to=x,h.dis=0,q.push(h); 33 while(!q.empty()) { 34 h=q.top(),q.pop(); 35 if(dis[h.to]<h.dis) continue ;// 36 for(int i=last[h.to];i;i=edge[i].nxt) { 37 y.to=edge[i].to,y.dis=edge[i].dis; 38 if(dis[y.to]>dis[h.to]+y.dis) { 39 dis[y.to]=dis[h.to]+y.dis; 40 y.dis=dis[y.to];// 41 q.push(y);// vis[y.to]=1; 42 } 43 } 44 } 45 } 46 struct main { 47 main() { 48 read(n),read(m),read(s); 49 for(register int i=1; i<=m; i++) { 50 read(from),read(to),read(w); 51 add(from,to,w); 52 } 53 dijkstra(s); 54 for(register int i=1; i<=n; i++) { 55 write(dis[i]),putchar(' '); 56 } 57 exit(0); 58 } 59 inline void read(int &x) { 60 x=0; 61 char ch=getchar(); 62 while (!isdigit(ch)) ch=getchar(); 63 while (isdigit(ch)) x=x*10+ch-'0',ch=getchar(); 64 return ; 65 } 66 inline void write(int x) { 67 if (x>9) write(x/10); 68 putchar(x%10+'0'); 69 return ; 70 } 71 } UniversalLove; 72 } 73 int main() { 74 Moxing::main(); 75 }
3.floyd
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 #include<queue> 9 using namespace std; 10 namespace Moxing { 11 const int N=5005; 12 const int inf=1061109567; 13 int a[N][N],n,m,x,y,w; 14 void floyd(){ 15 for(int k=1;k<=n;k++){ 16 for(int i=1;i<=n;i++){ 17 for(int j=1;j<=n;j++){ 18 a[i][j]=min(a[i][j],a[i][k]+a[k][j]); 19 } 20 } 21 } 22 } 23 void none(){ 24 printf("inf"); 25 } 26 struct main { 27 main() { 28 read(n),read(m); 29 memset(a,0x3f,sizeof(a)); 30 for(int i=1;i<=n;i++) a[i][i]=0; 31 for(int i=1;i<=m;i++){ 32 read(x),read(y),read(w); 33 a[x][y]=/*a[y][x]*/=min(a[x][y],w); 34 } 35 floyd(); 36 for(int i=1;i<=n;i++){ 37 for(int j=1;j<=n;j++){ 38 a[i][j]==inf?none():write(a[i][j]); 39 putchar(' '); 40 } 41 puts(""); 42 } 43 exit(0); 44 } 45 inline void read(int &x) { 46 x=0; 47 char ch=getchar(); 48 while (!isdigit(ch)) ch=getchar(); 49 while (isdigit(ch)) x=x*10+ch-'0',ch=getchar(); 50 return ; 51 } 52 inline void write(int x) { 53 if (x>9) write(x/10); 54 putchar(x%10+'0'); 55 return ; 56 } 57 } UniversalLove; 58 } 59 int main() { 60 Moxing::main(); 61 } 62 //4 6 63 //1 2 2 64 //2 3 2 65 //2 4 1 66 //1 3 5 67 //3 4 3 68 //1 4 4
4.次短路(A-star/dijkstra)
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<queue> 8 #include<vector> 9 #include<map> 10 #include<set> 11 using namespace std; 12 const int inf=2147483647; 13 int dis[1050],last[100005],last1[100005],cnt,sum,tim[100005],k; 14 struct node{ 15 int to,dis,nxt; 16 }fanedge[100005],edge[100005]; 17 struct nde{ 18 int to,dis; 19 bool friend operator < (nde a,nde b){ 20 return a.dis>b.dis; 21 } 22 }h,y; 23 priority_queue<nde>q; 24 void fanadd(int from,int to,int dis){ 25 fanedge[++cnt].to=from;fanedge[cnt].dis=dis;fanedge[cnt].nxt=last[to];last[to]=cnt; 26 } 27 void add(int from,int to,int dis){ 28 edge[++sum].to=to;edge[sum].dis=dis;edge[sum].nxt=last1[from];last1[from]=sum; 29 } 30 struct point{ 31 int to,h,g; 32 point (){} 33 point(int to,int h,int g):to(to),h(h),g(g){} 34 bool friend operator < (point a,point b){ 35 return a.h+a.g>b.h+b.g; 36 } 37 }p; 38 priority_queue<point>Q; 39 int Astar(int s,int e){ 40 if(dis[s]==inf) return -1; 41 Q.push(point(s,0,0)); 42 while(!Q.empty()){ 43 p=Q.top();Q.pop(); 44 //cout<<p.to<<" "<<p.h<<" "<<p.g<<endl; 45 tim[p.to]++; 46 if(tim[p.to]==k&&p.to==e){ 47 return p.g+p.h; 48 } 49 if(tim[p.to]>k) continue ; 50 for(int i=last1[p.to];i;i=edge[i].nxt){ 51 Q.push(point(edge[i].to,p.h+edge[i].dis,dis[edge[i].to])); 52 //cout<<"##"<<edge[i].to<<endl; 53 } 54 } 55 return -1; 56 } 57 void dijkstra(int x){ 58 fill(dis,dis+1050,inf); 59 dis[x]=0;h.dis=0;h.to=x; 60 q.push(h); 61 while(!q.empty()){ 62 h=q.top();q.pop(); 63 if(dis[h.to]<h.dis) continue ; 64 for(int i=last[h.to];i;i=fanedge[i].nxt){ 65 y.to=fanedge[i].to;y.dis=fanedge[i].dis; 66 if(dis[y.to]>dis[h.to]+y.dis){ 67 dis[y.to]=dis[h.to]+y.dis; 68 y.dis=dis[y.to]; 69 q.push(y); 70 } 71 } 72 } 73 } 74 inline int read(){ 75 int x=0,f=1;char ch=getchar(); 76 while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();} 77 while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); 78 return x*f; 79 } 80 int main(){ 81 int n,m,from,to,w,b,t; 82 //n=read();m=read(); 83 scanf("%d%d",&n,&m); 84 memset(last,-1,sizeof(last)); 85 memset(last1,-1,sizeof(last1)); 86 for(int i=1;i<=m;i++){ 87 //from=read();to=read();w=read(); 88 scanf("%d%d%d",&from,&to,&w); 89 fanadd(from,to,w); 90 add(from,to,w); 91 } 92 // b=read();t=read();k=read(); 93 scanf("%d%d%d",&b,&t,&k); 94 if (b==t) { 95 ++k; 96 } 97 dijkstra(t);//cout<<dis[b]<<" "; 98 // for(int i=1;i<=sum;i++){ 99 // cout<<edge[i].to<<" "<<edge[i].dis<<" "<<edge[i].nxt<<endl; 100 // } 101 printf("%d",Astar(b,t)); 102 }
5.负环判定
6.最小环
五、01分数规划
0-1分数规划问题是指:求出解集{xi|xi=0或1},使以下式子最大:
(c1*x1+c2*x2+...+cn*xn)/(d1*x1+d2*x2+…+dn*xn)。 其中c1~cn、d1~cn为常数,记分⼦子为cx,分母为dx,L=cx/dx。
设F(mid)=sigma(a[i]*x[i])-mid*sigma(b[i]*x[i]) 化简=sigma((a[i]-mid*b[i])*x[i]) 设d[i]=a[i]-mid*b[i] 则有F(mid)=sigma(d[i]*x[i])
我们假设F(mid)>0 则有sigma(a[i]*x[i])-mid*sigma(b[i]*x[i])>0 转化后得到sigma(a[i]*x[i])/sigma(b[i]*x[i])>mid
1 #include<iostream> 2 #include<cmath> 3 #include<cstdio> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 using namespace std; 9 namespace Moxing { 10 const int N=1005; 11 const double eps=1e-7; 12 double a[N],b[N],t[N]; 13 struct main { 14 main() { 15 int n,k; 16 while(~scanf("%d%d",&n,&k)) { 17 if(n==0&&k==0) return 0; 18 for(int i=1; i<=n; i++) { 19 scanf("%lf",&a[i]); 20 } 21 for(int i=1; i<=n; i++) { 22 scanf("%lf",&b[i]); 23 } 24 double l=0.0,r=1.0,mid,sum; 25 while(r-l>eps) { 26 mid=(l+r)*1.0/2,sum=0; 27 for(int i=1; i<=n; i++) { 28 t[i]=a[i]-b[i]*mid; 29 } 30 sort(t+1,t+1+n); 31 for(int i=k+1; i<=n; i++) { 32 sum+=t[i]; 33 } 34 if(sum>0) l=mid; 35 else r=mid; 36 } 37 printf("%.0f\n",mid*100); 38 } 39 exit(0); 40 41 } 42 } UniversalLove; 43 } 44 45 int main() { 46 47 Moxing::main(); 48 }
六、拓扑排序
1 #include<iostream> 2 #include<cmath> 3 #include<cstdio> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 using namespace std; 9 namespace Moxing { 10 vector<int>g[maxn]; 11 int n,m,indegree[maxn],ans[maxn];//n point;m 12 inline void topsort() { 13 priority_queue <int> q; 14 int tot=0,i; 15 for(i=1; i<=n; i++) if(!indegree[i]) q.push(i); 16 while(!q.empty()) { 17 int t=q.top(); 18 q.pop(),tot++,ans[tot]=t; 19 for(i=0; i<g[t].size(); i++) { 20 int go=g[t][i]; 21 indegree[go]--; 22 if(!indegree[go]) q.push(go); 23 } 24 } 25 if(tot<n) { 26 cout<<"Impossible!"<<endl;//Circle 27 } else { 28 for(i=tot; i>=1; i--) cout<<ans[i]<<' '; 29 cout<<endl; 30 } 31 } 32 struct main { 33 main() { 34 int d,i,h,t; 35 n=read(),m=read(); 36 for(i=1; i<=m; i++) { 37 h=read(),t=read(); 38 indegree[t]++; 39 g[h].push_back(t); 40 } 41 topsort(); 42 exit(0); 43 } 44 inline int read() { 45 int x=0,f=1; 46 char ch=getchar(); 47 while(ch>'9'||ch<'0') { 48 if(ch=='-') f=-1; 49 ch=getchar(); 50 } 51 while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); 52 return f*x; 53 } 54 } UniversalLove; 55 } 56 int main() { 57 Moxing::main(); 58 }
七、欧拉回路
若图G中存在这样一条路径,使得它恰通过G中每条边一次,则称该路径为欧拉路径。若该路径是一个圈,则称为欧拉(Euler)回路。 有向图: 欧拉路:所有点入度=出度;
或者存在一个点入度-出度=1,一个点出度-入度=1,其余点入度=出度。
欧拉回路:所有点入度=出度。 无向图: 欧拉路:所有点度数为偶数;或者有两个点度数为奇数,其余点度数为偶数 欧拉回路:所有点度数为偶数。
八、二分图
顶点可以分成A、B两个集合(后面也称作左部,右部),每条边的两个顶点分别位于A、B集合中的图被称为二分图。
二分图中不含奇环。
最小边覆盖=最大独立集=总节点数-最大匹配数
匹配:在图论中,匹配是指两两没有公共点的边的集合。
最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。
完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。(显然,完美匹配当且仅当最大匹配*2==点的个数)
我们定义匹配点、匹配边、未匹配点、非匹配边,它们的含义非常显然。
交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边...形成的路径叫交替路。
增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)
1.二分图判定
二分图的判定方法: 用DFS对二分图进行黑白染色。如果某个点染成黑色,那么与这个点相连的所有点都必须染成白色,反之同理。
如果染色过程中不出现矛盾,那么这就是一个二分图。
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 using namespace std; 9 namespace Moxing{ 10 const int N=100005; 11 int n,m,last[N],cnt,col[N]; 12 struct node{ 13 int to,nxt; 14 }edge[N<<1]; 15 void add(int from,int to){ 16 edge[++cnt].to=to,edge[cnt].nxt=last[from],last[from]=cnt; 17 } 18 bool dfs(int x,int r){ 19 col[x]=r; 20 for(int i=last[x];i;i=edge[i].nxt){ 21 int y=edge[i].to; 22 if(col[y]==col[x]) return 0; 23 if(!col[y]){ 24 //col[y]=3-col[x]; 25 if(!dfs(y,col[y])) return 0; 26 } 27 } 28 return 1; 29 } 30 void solve(){ 31 for(int i=1;i<=n;i++){ 32 if(col[i]) continue ; 33 if(!dfs(i,1)){ 34 puts("No"); 35 exit(0); 36 } 37 } 38 puts("Yes"); 39 } 40 struct main{ 41 main(){ 42 scanf("%d%d",&n,&m); 43 for(int i=1;i<=m;i++){ 44 int from,to; 45 scanf("%d%d",&from,&to); 46 add(from,to),add(to,from); 47 } 48 solve(); 49 exit(0); 50 } 51 }UniversalLove; 52 } 53 int main(){ 54 Moxing::main(); 55 }
2.二分图最大匹配
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #include<algorithm> 7 #include<iomanip> 8 using namespace std; 9 namespace Moxing{ 10 const int N=10005; 11 int n,m,last[N<<1],cnt,match[N<<1],ans;bool vis[N<<1]; 12 struct node{ 13 int to,nxt; 14 }edge[N<<2]; 15 void add(int from,int to){ 16 edge[++cnt].to=to,edge[cnt].nxt=last[from],last[from]=cnt; 17 } 18 bool dfs(int x){ 19 for(int i=last[x];i;i=edge[i].nxt){ 20 int y=edge[i].to; 21 if(vis[y]) continue ; 22 vis[y]=1; 23 if(!match[y]||dfs(y)){ 24 match[y]=x,match[x]=y;return 1; 25 } 26 } 27 return 0; 28 } 29 void solve(){ 30 for(int i=1;i<=n;i++){ 31 if(dfs(i)) ans++; 32 } 33 } 34 struct main{ 35 main(){ 36 scanf("%d%d",&n,&m); 37 for(int i=1;i<=m;i++){ 38 int x,y; 39 scanf("%d%d",&x,&y); 40 add(x,y+n); 41 } 42 solve(); 43 printf("%d\n",ans); 44 exit(0); 45 } 46 }UniversalLove; 47 } 48 int main(){ 49 Moxing::main(); 50 }
增广路算法(匈牙利算法) 匈牙利算法的本质是通过贪心从每个左部节点寻找交错路的过程。
依次考虑每个左部节点,为其找到一个右部节点与之匹配。
一个右部节点能与之匹配,必满足以下两个条件之一:
1.这个节点尚未与任何左部节点匹配,此时直接把两个节点进行匹配。
2.从该节点匹配的左部节点出发,可以找到另一个未标记的右部节点与之匹配,此时给该节点打上标记,然后递归进入那个左部节点,为它寻找匹配节点。
3.二分图最小覆盖
满足图中每一条边都有至少一个点在其之中的点集,被称为图的覆盖。
最小覆盖就是包含点数最少的覆盖。
二分图最小覆盖在数值上等于二分图最大匹配。
先求出最大匹配。 然后从右部的每一个未匹配点开始寻找交错路,并标记访问过的节点。
取左部标记的节点、右部未标记的节点,构成一组最小覆盖。
实际上是在每一条匹配边中,挑选一个左边的匹配点或者右边的匹配点。
最小覆盖的大小不可能小于最大匹配,因为那些匹配边的点两两不交。
König定理
上述二分图最大匹配等于最小覆盖定理叫做König定理
证明: 只需证明这样取出的点是一个点覆盖。 考虑二分图中的所有边。不可能存在一条边左右两端都是非匹配点。
(1)若这条边的右端点是匹配点,则它是未标记点,所以这条边可以被覆盖。
(2)若这条边的右端点是非匹配点,左端点是匹配点,则可以通过右端点遍历交错路时,标记这个左端点,所以这样是一个覆盖。
4.二分图最大独立集
任意两点在图中都没有边相连的点集被称为图的最大独立集。
二分图最大独立集=图的点数–二分图最大匹配
可以反向理解:在图中去掉最少的点,使剩下的点之间没有边。那么就是用最少的点覆盖所有的边,所以去掉的是最小覆盖。
5.最小路径覆盖
最小路径覆盖就是用尽量少的不相交简单路径覆盖有向无环图的所有顶点。
最小路径覆盖=节点数–最大匹配。
把原图中的每个点拆成二分图中左、右两个点,对于每条有向边(u,v),从u的左部点向v的右部点连一条有向边。
然后求最大匹配,用节点数减去。 拆点把原图最小路径覆盖中的“每个点的入度、出度均不超过1”转化为了二分图中“匹配边没有公共端点”。
最小路径覆盖里,路径上的每条边都对应着最大匹配中的一条匹配边,每条路径的最后一个点没有被匹配。 所以最小路径覆盖的路径数=最少未匹配的点数=节点数–最大匹配。
6.二分图最优匹配
最优匹配:二分图的每条边都有权值,权值和最大的匹配就是二分图的最优匹配。
完备匹配:左部或者右部点集中的所有点都被匹配。
顶标:给每个节点一个标号,记左部顶点标号为A[i],右部标号为B[i],称为顶标。 顶标满足:对于二分图中任意一条边(u,v),A[u]+B[v]>=edge(u,v)始终成立。
相等子图:二分图中所有满足A[u]+B[v]=edge(u,v)的边构成的子图。
相等子图的完备匹配就是二分图的最优匹配。
对于二分图的任意一个匹配,如果它包含于相等子图,那么其边权和等于节点的顶标和;如果它不包含于相等子图,那么其边权和<=节点的顶标和。 因此相等子图的完备匹配就是二分图的最优匹配。
KM算法
对交错树中左部节点的顶标增加一个值Δ,右部节点的顶标减小Δ。
(1)两端都在交错树中的边(u,v),A[u]+B[v]没有变化,仍然在相等子图中。
(2) 两端都不在交错树中的边(u,v),A[u]+B[v]没有变化,仍然不在相等子图中。
(3)左端不在、右端在交错树中的边(u,v),A[u]+B[v]增大,仍然不在相等子图中。
(4)左端在、右端不在交错树中的边(u,v),A[u]+B[v]减小,有可能进入相等子图。
为了使A[u]+B[v]>=edge(u,v)始终成立并且相等子图扩大,应该取: Δ=Min{A[u]+B[v]-edge(u,v) | u在交错树中,v不在交错树中}
九、图的连通性
1.有向图的强连通分量
在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通。
如果有向图G的每两个顶点都强连通,称G是一个强连通图。
非强连通图有向图的极大强连通子图,称为强连通分量。
2.tarjan算法
Tarjan算法是基于对图深度优先搜索的算法。
它维护了一个栈,搜索时,把与当前点相连的未访问的节点入栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
该算法引入了两个很重要的变量: Dfn(u)是节点u的时间戳,表示点u是第几个被访问的节点。 Low(u)是u或u的子树能够追溯到的最早的栈中节点的时间戳。
当Dfn(u)=Low(u)时,以u为根的搜索子树上所有节点(从栈顶到u的所有节点)是一个强连通分量。
Low数组的计算方法: 如果x→y是树枝边(在搜索树中的边),那么 low[x]=min(low[x],low[y])。
如果x→y是后向边(连接到已在栈中节点的边),那么 low[x]=min(low[x],dfn[y])。
如果x→y是横叉边(连接到已经出栈的节点的边),什么也不做。
1 void tarjan(int x){ 2 dfn[x]=low[x]=++tot; 3 s[++top]=x,ins[x]=1; 4 for(int i=last[x];i;i=edge[i].nxt){ 5 int y=edge[i].to; 6 if(!dfn[y]){ 7 tarjan(y); 8 low[x]=min(low[x],low[y]); 9 } 10 else if(ins[y]) low[x]=min(low[x],low[y]); 11 } 12 if(dfn[x]==low[x]){ 13 scc++; 14 int y; 15 do{ 16 y=s[top--]; 17 ins[y]=0; 18 }while(x!=y); 19 } 20 }
3.无向图上的tarjan算法
(1)割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点。
(2)割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。
(3)点连通度:最小割点集合中的顶点数。
(4)割边(桥):删掉它之后,图必然会分裂为两个或两个以上的子图。
(5)割边集合:如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。
(6)边连通度:一个图的边连通度的定义为,最小割边集合中的边数。
(7)缩点:把没有割边的连通子图缩为一个点,此时满足任意两点之间都有两条或更多路径可达。 缩点后变成一棵k个点k-1条割边连接成的树。
(8)双连通分量:分为点双连通和边双连通。它的标准定义为:点连通度大于1的图称为点双连通图,边连通度大于1的图称为边双连通图。通俗地讲,满足任意两点之间,能通过两条或两条以上没有任何重复边的路到达的图称为双连通图。无向图G的极大双连通子图称为双连通分量。
(9)最近公共祖先:同时是x和y的祖先的深度最大的节点称为x和y的最近公共祖先,记为LCA(x,y)。
4.tarjan求割点
Tarjan算法中,我们得到了dfn和low两个数组,
low[u]:=min(low[u],dfn[v])——(u,v)为后向边;
low[u]:=min(low[u],low[v])——(u,v)为树枝边,v为u的子树;
若low[v]>=dfn[u],则u为割点,节点v的子孙和节点u形成一个块。
因为这说明v的子孙不能够通过其他边到达u的祖先,这样去掉u之后,图必然分裂为两个子图。
这样我们处理点u时,首先递归u的子节点v,然后从v回溯至u后,如果发现上述不等式成立,则找到了一个割点u,并且u和v的子树构成一个块
1 void tarjan(int x){//cut[x] 2 dfn[x]=low[x]=++num; 3 int flag=0; 4 for(int i=last[x];i;i=edge[i].nxt){ 5 int y=edge[i].to; 6 if(!dfn[y]){ 7 tarjan(y,i); 8 low[x]=min(low[x],low[y]); 9 if(low[y]>=dfn[x]){ 10 flag++; 11 if(x!=root||flag>1) cut[x]=1; 12 } 13 } 14 else low[x]=min(low[x],dfn[y]); 15 } 16 }
5.tarjan求割边
若low[v]>dfn[u],则(u,v)为割边。
但是实际处理时我们并不这样判断,因为有的图上可能有重边,这样不好处理。
我们记录每条边的标号(一条无向边拆成的两条有向边标号相同),记录每个点的父亲到它的边的标号,如果边(u,v)是v的父亲边,就不能用dfn[u]更新low[v]。
这样如果遍历完v的所有子节点后,发现low[v]=dfn[v],说明v的父亲边(u,v)为割边。
1 void tarjan(int x,int inedge){//brdige 2 dfn[x]=low[x]=++num; 3 for(int i=last[x];i;i=edge[i].nxt){ 4 int y=edge[i].to; 5 if(!dfn[y]){ 6 tarjan(y,i); 7 low[x]=min(low[x],low[y]); 8 if(low[y]>dfn[x]) bridge[i]=bridge[i^1]=1; 9 } 10 else if(i!=(inedge^1)) low[x]=min(low[x],dfn[y]); 11 } 12 }
6.点双联通分支
对于点双连通分支,实际上在求割点的过程中就能顺便把每个点双连通分支求出。
建立一个栈,存储当前双连通分支,访问每个点时把这个点入栈。 如果某时满足Dfn(u)<=Low(v),说明u是一个割点,那么把点从栈顶一个个取出,直到遇到了点v,取出的这些点和u组成一个点双连通分支。
割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m,last[N],low[N],dfn[N],head[N],num,tc,cnt,bridge[N<<1],c[N]; 4 struct node { 5 int to,nxt; 6 } edge[N<<1],vc[N<<1]; 7 void add(int from,int to) { 8 edge[++cnt].to=to,edge[cnt].nxt=last[from],last[from]=cnt; 9 } 10 void tarjan(int x,int inedge) { //brdige 11 dfn[x]=low[x]=++num; 12 for(int i=last[x]; i; i=edge[i].nxt) { 13 int y=edge[i].to; 14 if(!dfn[y]) { 15 tarjan(y,i); 16 low[x]=min(low[x],low[y]); 17 if(low[y]>dfn[x]) bridge[i]=bridge[i^1]=1; 18 } else if(i!=(inedge^1)) low[x]=min(low[x],dfn[y]); 19 } 20 } 21 void dfs(int x) { //e-DCC 22 c[x]=dcc; 23 for(int i=last[x]; i; i=edge[i].nxt) { 24 int y=edge[i].to; 25 if(c[y]||bridge[i]) continue ;//need brdige 26 dfs(y); 27 } 28 } 29 void addc(int from,int to) { //suodian need bridge&e-DCC 30 vc[++tc].to=to,vc[tc].nxt=head[from],head[from]=tc; 31 } 32 int main() { 33 scanf("%d%d",&n,&m); 34 for(int i=1; i<=m; i++) { 35 int from,to; 36 scanf("%d%d",&from,&to); 37 add(from,to),add(to,from); 38 } 39 for(int i=1; i<=n; i++) { 40 if(!dfn[i]) tarjan(i,0);//brdige 41 //if(!dfn[i]) root=i,tarjan(i);//cut[x] 42 if(!c[i]) ++dcc,dfs(i);//e-DCC 43 44 } 45 for(int i=2; i<=cnt; i++) { //suodian need e-DCC&cut[x] 46 int x=edge[i^1].to; 47 int y=edge[i].to; 48 if(c[x]==c[y]) continue ; 49 addc(c[x],c[y]); 50 } 51 }
7.边双连通分支
对于边双连通分支,求法更为简单。
只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。
桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。