图论基础概念戳这里oiwiki
1.图的存储与遍历
图是相对线性数据结构更高级的模型,可以1对多也可以多对1,点与点之间,边与边之间都可以加上更多的限制,使其变得非常灵活,下面介绍两种最常用的图的存储方法
1.1 vector存图
用vector存图,会比较直观好懂
先弄出一个结构体, v v v为到达的顶点, w w w为这条边的边权
struct edge{
int v,w;
};
vector<edge>e[maxn];
存储
e[u].push_back((edge){v,w});
遍历
for(auto x:e[u]){
int v=x.v;
int w=x.w;
}
1.2链式前向星式存图
int head[maxn],cnt;
struct edge{
int v,w,nex;
}e[maxn];
inline void add(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
}
遍历
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
int w=e[i].w;
}
1.3 DFS
int vis[maxn];
void dfs(int u){
if(vis[u])return;
vis[u]=1;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!vis[v])dfs(v);
}
return;
}
1.4 BFS
int vis[maxn];
void bfs(int s){
queue<int>q;
q.push(s);
vis[s]=1;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!vis[v]){
q.push(v);
vis[v]=1;
}
}
}
}
2.最短路问题
最短路是图论中最简单也是比较常用的算法之一
2.1 Floyd算法
Floyd是基于dp思想进行求最短路,代码仅仅四行,非常好记,可以处理负权
算法复杂度
O
(
N
3
)
O(N^3)
O(N3)
void floyd(int n){
memset(dis,0x3f,sizeof dis);
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
}
2.2 SPFA算法
spfa算法是基于bellman-ford算法的队列优化,可以处理负权,在随机图的情况下跑的速度非常快,接近于线性,最坏复杂度 O ( N ∗ M ) O(N*M) O(N∗M),正权图请不要用spfa求最短路,会被卡到最坏复杂度
void spfa(int s){
queue<int>q;
memset(dis,0x3f,sizeof dis);
memset(inque,0,sizeof inque);
q.push(s);
inque[s]=1;
dis[s]=0;
while(!q.empty()){
int u=q.front();
q.pop();
inque[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
int w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!inque[v]){
q.push(v);
inque[v]=1;
}
}
}
}
}
2.3 Dijkstra算法
堆优化dijkstra算法的复杂度非常优秀,为
O
(
M
∗
l
o
g
N
)
O(M*logN)
O(M∗logN),缺点是不能处理负权
需要对优先队列的<符号进行重载
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e5+5;
constexpr int inf=0x3f3f3f3f;
typedef pair<int,int>pii;
int cnt,n,m,s,vis[maxn],dis[maxn];
struct edge{
int v,w;
};
vector<edge>e[maxn];
priority_queue<pii,vector<pii>,greater<pii>>q;
inline void dij(int s){
memset(dis,inf,sizeof dis);
memset(vis,0,sizeof vis);
dis[s]=0;
q.push({0,s});
while(!q.empty()){
pii x=q.top();
q.pop();
int u=x.second;
if(vis[u])continue;
vis[u]=1;
for(auto ss:e[u]){
int v=ss.v;
if(dis[v]>dis[u]+ss.w){
dis[v]=dis[u]+ss.w;
q.push({dis[v],v});
}
}
}
}
signed main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e[u].push_back((edge){v,w});
}
dij(s);
for(int i=1;i<=n;i++){
printf("%d ",dis[i]);
}
return 0;
}
2.4 Johnson全源最短路算法
比Floyd更快的全源最短路算法
前置需要掌握:堆优化dij算法+spfa判环
用势能转化,使得dij可以跑负权图,复杂度 O ( N ∗ M ∗ l o g N ) O(N*M*logN) O(N∗M∗logN)
为什么这样是对的,详情请看oiwiki
#include<bits/stdc++.h>
using namespace std;
#define inf 1e9
const int maxn=5005,maxm=10005;
struct edge{
int v,w,nex;
}e[maxm];
struct node{
int dis,id;
bool operator<(const node&a)const
{
return dis>a.dis;
}
node(int d,int x){
dis=d,id=x;
}
};
int head[maxn],vis[maxn],cs[maxn];
int cnt,n,m;
long long h[maxn],dis[maxn];
inline void add(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
}
bool spfa(int s){
queue<int>q;
memset(h,63,sizeof(h));
h[s]=0,vis[s]=1;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(h[v]>h[u]+e[i].w){
h[v]=h[u]+e[i].w;
if(!vis[v]){
vis[v]=1;
q.push(v);
cs[v]++;
if(cs[v]==n)return false;
}
}
}
}
return true;
}
inline void dij(int s){
priority_queue<node>q;
for(int i=1;i<=n;i++)
dis[i]=inf;
memset(vis,0,sizeof(vis));
dis[s]=0;
q.push(node(0,s));
while(!q.empty()){
int u=q.top().id;
q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(dis[v]>dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;
if(!vis[v])q.push(node(dis[v],v));
}
}
}
return;
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
add(u,v,w);
}
for(int i=1;i<=n;i++){
add(0,i,0);
}
if(!spfa(0)){
cout<<-1<<endl;
return 0;
}
for(int u=1;u<=n;u++)
for(int i=head[u];i;i=e[i].nex)
e[i].w+=h[u]-h[e[i].v];
for(int i=1;i<=n;i++){
dij(i);
long long ans=0;
for(int j=1;j<=n;j++){
if(dis[j]==inf)ans+=j*inf;
else ans+=j*(dis[j]+h[j]-h[i]);
}
cout<<ans<<endl;
}
return 0;
}
3.树上问题
树是一种特殊的图,有很多很好的性质
3.1 树的直径
树的直径的定义为树上距离最远的两点的距离
树的直径有两种求法,两次dfs和树形dp
各有优劣,dfs可以保存路径,树形dp可以处理负权
3.1.1 两次dfs求树的直径
int dis[500];
void dfs(int u,int fa){
if(dis[u]>dis[pos]||pos==st)pos=u;
f[u]=fa;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(v==fa)continue;
dis[v]=dis[u]+e[i].w;
dfs(v,u);
}
return;
}
inline int zj(){
int st,pos;
st=1.pos=1;
dis[1]=0;
dfs(st,0);
st=pos;
dis[st]=0;
dfs(st,0);
ed=pos;
return dis[ed];
}
3.1.2 树形dp
#include <iostream>
#include <vector>
using namespace std;
const int N = int(1e4) + 9;
vector<int> adj[N];
int n, d;
int dfs(int u = 1, int p = -1) {
int d1 = 0, d2 = 0;
for (auto v : adj[u]) {
if (v == p) continue;
int d = dfs(v, u) + 1;
if (d > d1)
d2 = d1, d1 = d;
else if (d > d2)
d2 = d;
}
d = max(d, d1 + d2);
return d1;
}
int main() {
cin >> n;
for (int i = 0; i < n - 1; ++i) {
int a, b;
cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
dfs();
cout << d << endl;
}
3.2 树的重心
对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心
void dfs(int u,int f){
d[u]=1;
int res=0;
for(auto v:e[u]){
if(v==f)continue;
dfs(v,u);
d[u]+=d[v];
res=max(res,d[v]);
}
res=max(res,n-d[u]);
if(res<maxn||(res==maxn&&ans>u)){
maxn=res;
ans=u;
}
}
3.3 最近公共祖先(LCA)
最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。
l c a lca lca有倍增法 和 t a r j a n tarjan tarjan算法离线求解
还有树剖可以快速求解,常数较小
这里介绍倍增法
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=5e5+5,maxm=1e6+5;
int head[maxm],cnt;
int dep[maxn],f[maxn][22],lg[maxn];
struct edge{
int v,nex;
}e[maxm];
void add(int u,int v){
e[++cnt].v=v;
e[cnt].nex=head[u];
head[u]=cnt;
}
inline void pre(int n){//预先算出log_2(i)+1的值,用的时候直接调用就可以了
for(int i=1;i<=n;i++){
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
}
void dfs(int u,int fa){ //u表示当前节点, fa表示父亲节点
f[u][0]=fa;
dep[u]=dep[fa]+1;
for(int i=1;i<=lg[dep[u]];i++){
f[u][i]=f[f[u][i-1]][i-1];//意思是u的2^i祖先等于u的2^(i-1)祖先的2^(i-1)祖先
} //2^i = 2^(i-1) + 2^(i-1)
for(int i=head[u];i;i=e[i].nex){
if(e[i].v==fa)continue;
dfs(e[i].v,u);
}
}
inline int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);//用数学语言来说就是:不妨设x的深度 >= y的深度
while(dep[u]>dep[v]){
u=f[u][lg[dep[u]-dep[v]]-1];//先跳到同一深度
}
if(u==v)return u;//如果x是y的祖先,那他们的LCA肯定就是x了
for(int i=lg[dep[u]]-1;i>=0;i--){//不断向上跳(lg就是之前说的常数优化)
if(f[u][i]!=f[v][i]){//因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
u=f[u][i];
v=f[v][i];
}
}
return f[u][0];//返回父节点
}
int main(){
int n,m,s;
cin>>n>>m>>s;
pre(n);
for(int i=1;i<=n-1;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs(s,0);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",lca(u,v));
}
return 0;
}
3.4 点分治
点分治适合处理大规模的树上路径信息问题。
3.5 树链剖分
树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。
具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。
重链剖分维护两点路径上的权值和某点子树的权值:
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=1e5+5,inf=0x3f3f3f3f;
vector<int>e[maxn];
int dep[maxn],f[maxn],sz[maxn],son[maxn];
int id[maxn],wt[maxn],w[maxn],top[maxn],cnt;
int b[maxn*3],d[maxn*3];
int n,m,r,mod;
inline void update(int p){
d[p]=d[p*2]+d[p*2+1];
d[p]%=mod;
}
void build(int l,int r,int p){
if(l==r){
d[p]=wt[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,2*p),build(mid+1,r,2*p+1);
update(p);
}
void pushdown(int l,int r,int s,int t,int mid,int p){
d[p*2]+=b[p]*(mid-s+1);
d[p*2+1]+=b[p]*(t-mid);
b[p*2]+=b[p];
b[p*2+1]+=b[p];
b[p]=0;
}
void add(int l,int r,int c,int s,int t,int p){
if(l<=s&&t<=r){
d[p]+=(t-s+1)*c;
b[p]+=c;
return;
}
int mid=(s+t)/2;
if(b[p]&&s!=t){
pushdown(l,r,s,t,mid,p);
}
if(l<=mid)add(l,r,c,s,mid,p*2);
if(r>mid) add(l,r,c,mid+1,t,p*2+1);
update(p);
}
int getsum(int l,int r,int s,int t,int p){
if(l<=s&&t<=r)return d[p];
int mid=(s+t)/2;
if(b[p]){
pushdown(l,r,s,t,mid,p);
}
int sum=0;
if(l<=mid)sum+=getsum(l,r,s,mid,p*2);
if(r>mid) sum+=getsum(l,r,mid+1,t,p*2+1);
return sum;
}
void dfs1(int u,int fa,int depth){
dep[u]=depth;
f[u]=fa;
sz[u]=1;
int maxson=-1;
for(auto v:e[u]){
if(v==fa)continue;
dfs1(v,u,depth+1);
sz[u]+=sz[v];
if(sz[v]>maxson){
maxson=sz[v];
son[u]=v;
}
}
}
void dfs2(int u,int topf){
id[u]=++cnt;
wt[cnt]=w[u];
top[u]=topf;
if(!son[u])return;
dfs2(son[u],topf);
for(auto v:e[u]){
if(v==f[u]||v==son[u])continue;
dfs2(v,v);
}
}
int range_getsum(int x,int y){
int ans=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
ans+=getsum(id[top[x]],id[x],1,n,1);
ans%=mod;
x=f[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
ans+=getsum(id[x],id[y],1,n,1);
return ans%mod;
}
void range_add(int x,int y,int c){
c%=mod;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
add(id[top[x]],id[x],c,1,n,1);
x=f[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
add(id[x],id[y],c,1,n,1);
}
inline int son_getsum(int x){
return (getsum(id[x],id[x]+sz[x]-1,1,n,1))%mod;
}
inline void son_add(int x,int c){
add(id[x],id[x]+sz[x]-1,c,1,n,1);
}
int lca(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]])u = f[top[u]];
else v = f[top[v]];
}
return dep[u] > dep[v] ? v : u;
}
int main(){
cin>>n>>m>>r>>mod;
for(int i=1;i<=n;i++){
cin>>w[i];
}
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
dfs1(r,r,1);
dfs2(r,r);
build(1,n,1);
for(int i=1;i<=m;i++){
int ch,x,y,z;
cin>>ch;
if(ch==1){
cin>>x>>y>>z;
range_add(x,y,z);
}
else if(ch==2){
cin>>x>>y;
printf("%lld\n",range_getsum(x,y));
}
else if(ch==3){
cin>>x>>y;
son_add(x,y);
}
else{
cin>>x;
printf("%lld\n",son_getsum(x));
}
}
return 0;
}
4.生成树
我们定义无向连通图的 最小生成树 (Minimum Spanning Tree,MST)为边权和最小的生成树。
注意:只有连通图才有生成树,而对于非连通图,只存在生成森林
4.1.Kruskal算法
k r u s k a l kruskal kruskal是基于并查集的一种求解最小生成树的算法,复杂度非常优秀,求解的思想是优先选择最小边,若两端点不属于同一祖先,则加上
#include<bits/stdc++.h>
using namespace std;
int n,m,a[5005],ans,cnt;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
struct edge{
int u,v,w;
}e[200005];
int find(int x){
if(a[x]==x)return x;
return a[x]=find(a[x]);
}
void hb(int y,int x){
a[find(y)]=find(x);
return;
}
bool cmp(edge a,edge b){
return a.w<b.w;
}
void krus(){
sort(e+1,e+1+m,cmp);
for(int i=1;i<=m;i++){
int u,v;
u=e[i].u;
v=e[i].v;
if(find(u)==find(v))continue;
ans+=e[i].w;
hb(u,v);
if(++cnt==n-1)break;
}
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=i;
}
for(int i=1;i<=m;i++){
e[i].u=read();
e[i].v=read();
e[i].w=read();
}
krus();
cout<<ans;
return 0;
}
4.2堆优化Prim算法
p r i m prim prim算法的思想跟 d i j dij dij一样,是选取一个点,并贪心的选择与这个点边权最小的边,并也可用优先队列来优化,在稠密图下复杂度表现良好,非稠密图不建议使用
#include<bits/stdc++.h>
using namespace std;
int k,n,m,cnt,ans,sum;
int dis[5005],vis[5005];
struct edge{
int v,w;
};
struct node{
int w,now;
bool operator <(const node&x)const
{
return w>x.w;
}
};
vector<edge>g[5005];
priority_queue<node>q;
void prim(){
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
q.push((node){0,1});
while(!q.empty()&&cnt<n){
node x=q.top();
q.pop();
int d=x.w;
int u=x.now;
if(vis[u])continue;
cnt++;
sum+=d;
vis[u]=1;
for(int i=0;i<g[u].size();i++){
if(g[u][i].w<dis[g[u][i].v]){
dis[g[u][i].v]=g[u][i].w;
q.push((node){dis[g[u][i].v],g[u][i].v});
}
}
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
g[u].push_back((edge){v,w});
g[v].push_back((edge){u,w});
}
prim();
if(cnt==n)cout<<sum;
else cout<<"orz";
return 0;
}
4.3最小树形图-朱刘算法
最小树形图就是有向图的最小生成树
kuangbin最小树形图模板
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
const int inf =0x3f3f3f3f;
const int maxn=1005;
struct Edge{
int u, v, w;
Edge(int uu, int vv, int ww){u = uu, v = vv, w = ww;}
Edge(){}
}es[maxn*maxn];
struct pos{
double x,y;
}pos[105];
int pre[maxn], id[maxn], vis[maxn], in[maxn];
int zhuliu(int root, int n, int m){
int res = 0, u, v;
while(true){
fill(in, in+n, inf);
for(int i = 0; i < m; ++i)
if(es[i].u != es[i].v && es[i].w < in[es[i].v]){
pre[es[i].v] = es[i].u;
in[es[i].v] = es[i].w;
}
for(int i = 0; i < n; ++i)
if(i!=root && in[i]==inf)
return -1;
int tn = 0;
memset(id,-1,sizeof(id));
memset(vis,-1,sizeof(vis));
in[root] = 0;
for(int i = 0; i < n; ++i){
res += in[i];
v = i;
while(vis[v]!=i && id[v]==-1 && v!=root){
vis[v] = i;
v = pre[v];
}
if(v!=root && id[v]==-1){
for(int u = pre[v]; u != v; u = pre[u])
id[u] = tn;
id[v] = tn++;
}
}
if(tn == 0) break;
for(int i = 0; i < n; ++i)
if(id[i]==-1)
id[i] = tn++;
for(int i = 0; i < m;){
v = es[i].v;
es[i].u = id[es[i].u];
es[i].v = id[es[i].v];
if(es[i].u != es[i].v)
es[i++].w -= in[v];
else
swap(es[i], es[--m]);
}
n = tn;
root = id[root];
}
return res;
}
int n,m,w[1005][1005];
int main(){
int tt,a,b,c;
while(scanf("%d%d",&n,&m)!=EOF){
memset(w,inf,sizeof(w));
cin>>n>>m;
for(int i=0;i<m;i++){
scanf("%d%d%d",&pos[i].x,&pos[i].y);
// if(a==b)continue;
// w[a][b]=min(w[a][b],c);
}
int l=0;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
}
}
int ans=zhuliu(0,n,l);
printf("Case #%d: ",qq);
if(ans==-1) printf("Possums!\n");
else printf("%d\n",ans);
}
return 0;
}
5.拓扑排序
拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点。
void topo(){
queue<int>q;
for(int i=1;i<=n;i++){
if(du[i]==0){
q.push(i);
vis[i]=1;
}
}
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
du[v]--;
if(!vis[v]&&du[v]==0){
vis[v]=1;
q.push(v);
}
}
}
}
6.差分约束
7.图的连通性问题
强连通的定义是:有向图 G 强连通是指,G 中任意两个结点连通。
强连通分量(Strongly Connected Components,SCC)的定义是:极大的强连通子图。
7.1 强连通分量缩点
void tarjan(int u){
dfn[u]=low[u]=++tot;
z[++top]=u;//入栈
vis[u]=1;
for(auto v:e[u]){
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
t++;//连通分量的标号
do{
color[z[top]]=t; //属于这个连通分量
cnt[t]++; //记录这个环中有多少个点
vis[z[top]]=0;
top--; //出栈
}while(u!=z[top+1]);
}
}
7.2 双连通分量
在一张连通的无向图中,对于两个点u和v,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说u和v边双连通 。
在一张连通的无向图中,对于两个点 u和v,如果无论删去哪个点(只能删去一个,且不能删u和v自己)都不能使它们不连通,我们就说u和v点双连通 。
边双连通具有传递性,即,若x,y边双连通, y,z 边双连通,则 x,z 边双连通。
点双连通 不 具有传递性,反例如下图,a,b点双连通, b,c点双连通,而a,c不点双连通。
7.3 割点
对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点(又称割顶)。
void tarjan(int u,int root){//root代表此树的根
int child=0;
dfn[u]=low[u]=++tot;
for(int i=0;i<e[u].size();i++){
int v=e[u][i];
if(!dfn[v]){
tarjan(v,root);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=root)cut[u]=true;
if(u==root)child++;
}
low[u]=min(low[u],dfn[v]);
}
if(u==root&&child>=2){
cut[root]=true;
}
}
7.4 桥
对于一个无向图,如果删掉一条边后图中的连通分量数增加了,则称这条边为桥或者割边。
void tarjan(int u,int root){
dfn[u]=low[u]=++tot;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]){
qiao[i]=1;
}
}
else if(i!=(root^1))low[u]=min(low[u],dfn[v]);
}
return;
}
8.二分图最大匹配问题
二分图,又称二部图,英文名叫 Bipartite graph。
二分图是什么?节点由两个集合组成,且两个集合内部没有边的图。
换言之,存在一种方案,将节点划分成满足以上性质的两个集合。
如图:
8.1 匈牙利算法
常数非常优秀,时间复杂度 O ( N ∗ M ) O(N*M) O(N∗M)
bool dfs(int u){
for(int v=m+1;v<=n;v++){
if(vis[v]||!e[u][v])continue;
vis[v]=1;
if(!match[v]||dfs(match[v])){// 如果v没有匹配,或者v的匹配找到了新的匹配
match[v]=u;
match[u]=v; // 更新匹配信息
return true;
}
}
return false;
}
int xyl(){
int ans=0;
memset(match,0,sizeof(match));
for(int i=1;i<=n;i++){// 对每一个点尝试匹配
memset(vis,0,sizeof(vis));
if(dfs(i))ans++;
}
return ans;
}
8.2 Dinic求二分图最大匹配
建完二分图直接跑最大流即可,常数比匈牙利算法大,复杂度稍优秀,时间复杂度 O ( s q r t ( n ) ∗ M ) O(sqrt(n)*M) O(sqrt(n)∗M)
8.3 二分图最大权完美匹配问题-KM算法
缺陷在于只能用于求解完美匹配的二分图最大权问题,时间复杂度为 O ( N 3 ) O(N^3) O(N3), 相较于 S P F A SPFA SPFA费用流的 O ( N ∗ M ∗ F ) O(N*M*F) O(N∗M∗F)高效很多
(原理我没去弄懂,当黑盒用)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const long long maxn=1005,inf=9e18;
int n,m,mp[maxn][maxn],matched[maxn];
int slack[maxn],ex[maxn],ey[maxn],pre[maxn];
int visx[maxn],visy[maxn];
void match(int u){
int x,y=0,yy=0,d;
memset(pre,0,sizeof(pre));
for(int i=1;i<=n;i++)slack[i]=inf;
matched[y]=u;
while(1){
x=matched[y];
d=inf;
visy[y]=1;
for(int i=1;i<=n;i++){
if(visy[i])continue;
if(slack[i]>ex[x]+ey[i]-mp[x][i]){
slack[i]=ex[x]+ey[i]-mp[x][i];
pre[i]=y;
}
if(slack[i]<d){
d=slack[i];
yy=i;
}
}
for(int i=0;i<=n;i++){
if(visy[i])ex[matched[i]]-=d,ey[i]+=d;
else slack[i]-=d;
}
y=yy;
if(matched[y]==-1)break;
}
while(y){
matched[y]=matched[pre[y]];
y=pre[y];
}
}
int km(){
memset(matched,-1,sizeof(matched));
memset(ex,0,sizeof(ex));
memset(ey,0,sizeof(ey));
for(int i=1;i<=n;i++){
memset(visy,0,sizeof(visy));
match(i);
}
int ans=0;
for(int i=1;i<=n;i++){
if(matched[i]!=-1)ans+=mp[matched[i]][i];
}
return ans;
}
signed main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
mp[i][j]=-inf;
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%lld%lld%lld",&u,&v,&w);
mp[u][v]=w;
}
printf("%lld\n",km());
for(int i=1;i<=n;i++){
printf("%lld ",matched[i]);
}
return 0;
}
9.一般图最大匹配问题
一般图匹配和二分图匹配(bipartite matching)不同的是,图可能存在奇环。
9.1 一般图最大匹配算法-带花树
用于求解一般图最大匹配问题
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+5,maxm=2e5+5;
int n,m,cnt=1,head[maxn];
int match[maxn],pre[maxn],f[maxn],vis[maxn],tim,dfn[maxn];
struct edge{
int v,nex;
}e[maxm];
inline void add(int u,int v){
e[++cnt].v=v;
e[cnt].nex=head[u];
head[u]=cnt;
}
int find(int x){
return x==f[x]?x:f[x]=find(f[x]);
}
int lca(int u,int v){
++tim;
u=find(u);
v=find(v);
while(dfn[u]!=tim){
dfn[u]=tim;
u=find(pre[match[u]]);
if(v)swap(u,v);
}
return u;
}
queue<int>q;
void blossom(int x,int y,int w){
while(find(x)!=w){
pre[x]=y;
y=match[x];
if(vis[y]==2){
vis[y]=1;
q.push(y);
}
if(find(x)==x)f[x]=w;
if(find(y)==y)f[y]=w;
x=pre[y];
}
}
bool aug(int s){
for(int i=1;i<=n;i++){
f[i]=i;
vis[i]=pre[i]=0;
}
while(!q.empty()){
q.pop();
}
q.push(s);
vis[s]=1;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(find(u)==find(v)||vis[v]==2)continue;
if(!vis[v]){
vis[v]=2;
pre[v]=u;
if(!match[v]){
for(int x=v,last;x;x=last){
last=match[pre[x]];
match[x]=pre[x];
match[pre[x]]=x;
}
return true;
}
vis[match[v]]=1;
q.push(match[v]);
}
else{
int w=lca(u,v);
blossom(u,v,w);
blossom(v,u,w);
}
}
}
return false;
}
int main(){
cin>>n>>m;
int ans=0;
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++){
if(!match[i])ans+=aug(i);
}
printf("%d\n",ans);
for(int i=1;i<=n;i++){
printf("%d ",match[i]);
}
return 0;
}
9.2 一般图最大权匹配-带权带花树
原子弹级算法,几乎不太可能遇(遇到了也做不出)
贴上uoj的板子:一般图最大权匹配
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define dist(e) (lab[e.u]+lab[e.v]-g[e.u][e.v].w*2)
const int N = 1023, inf = 1e9;
struct edge{int u, v, w;}g[N][N];
int n, m, n_x, lab[N], match[N], slk[N], st[N];
int pa[N], ff[N][N], S[N], vis[N];
vector <int> flower[N];
deque <int> q;
void update(int u, int x) {//updateSlk
if (!slk[x] || dist(g[u][x]) < dist(g[slk[x]][x]))
slk[x] = u;
}
void setSlk(int x) {
slk[x] = 0;
for (int u = 1; u <= n; u ++)
if (g[u][x].w > 0 && st[u] != x && S[st[u]] == 0)
update(u, x);
}
void qpush(int x) {
if (x <= n) return q.push_back(x);
for (int i : flower[x]) qpush(i);
}
void setSt(int x, int b) {
st[x] = b; if (x <= n) return;
for (int i : flower[x]) setSt(i, b);
}
int getPr(int b, int xr) {
int pr = find(flower[b].begin(), flower[b].end(), xr) - flower[b].begin();
if (pr & 1) {
reverse(flower[b].begin() + 1, flower[b].end());
return (int)(flower[b].size()) - pr;
}
return pr;
}
void setMatch(int u, int v) {
match[u] = g[u][v].v; if (u <= n) return;
int xr = ff[u][g[u][v].u], pr= getPr(u, xr);
for (int i = 0; i < pr; i ++)
setMatch(flower[u][i], flower[u][i ^ 1]);
setMatch(xr, v);
rotate(flower[u].begin(), flower[u].begin() + pr, flower[u].end());
}
void augment(int u, int v) {
int xnv = st[match[u]]; setMatch(u, v);
if (!xnv) return; setMatch(xnv, st[pa[xnv]]);
augment(st[pa[xnv]], xnv);
}
int getLca(int u, int v) {
static int t = 0;
for (t ++; u || v; swap(u, v)) {
if (!u) continue;
if (vis[u] == t) return u;
vis[u] = t, u = st[match[u]];
if (u) u = st[pa[u]];
}
return 0;
}
void addBlossom(int u, int lca, int v) {
int b = n + 1;
while (b <= n_x && st[b]) b ++;
if (b > n_x) n_x ++;
lab[b] = 0, S[b] = 0; match[b] = match[lca];
flower[b].clear(); flower[b].push_back(lca);
for (int x = u,y; x != lca; x = st[pa[y]])
flower[b].push_back(x), flower[b].push_back(y = st[match[x]]), qpush(y);
reverse(flower[b].begin() + 1, flower[b].end());
for (int x = v, y; x != lca; x = st[pa[y]])
flower[b].push_back(x), flower[b].push_back(y = st[match[x]]), qpush(y);
setSt(b, b);
for (int x = 1; x <= n_x; ++ x) g[b][x].w = g[x][b].w = 0;
for (int x = 1; x <= n; ++ x) ff[b][x] = 0;
for (int i = 0; i < flower[b].size(); ++ i) {
int xs = flower[b][i];
for (int x = 1; x <= n_x; ++ x)
if (g[b][x].w == 0 || dist(g[xs][x]) < dist(g[b][x]))
g[b][x] = g[xs][x], g[x][b] = g[x][xs];
for (int x = 1; x <= n; ++ x)
if (ff[xs][x]) ff[b][x] = xs;
}
setSlk(b);
}
void expandBlossom(int b) {
for (int i = 0; i < flower[b].size(); ++ i)
setSt(flower[b][i], flower[b][i]);
int xr = ff[b][g[b][pa[b]].u], pr = getPr(b, xr);
for (int i = 0; i < pr; i += 2) {
int xs = flower[b][i], xns = flower[b][i + 1];
pa[xs] = g[xns][xs].u; S[xs] = 1, S[xns] = 0;
slk[xs] = 0, setSlk(xns); qpush(xns);
}
S[xr] = 1, pa[xr] = pa[b];
for (int i = pr + 1; i < flower[b].size(); ++ i) {
int xs = flower[b][i];
S[xs] = -1, setSlk(xs);
}
st[b] = 0;
}
bool onFoundedge(const edge &e) {
int u = st[e.u], v = st[e.v];
if (S[v] == -1) {
pa[v] = e.u, S[v] = 1;
int nu = st[match[v]];
slk[v] = slk[nu] = 0;
S[nu] = 0, qpush(nu);
}
else if (S[v] == 0) {
int lca = getLca(u, v);
if (!lca) return augment(u, v), augment(v, u), 1;
else addBlossom(u, lca, v);
}
return 0;
}
bool matching() {
fill(S, S + n_x + 1, -1), fill(slk, slk + n_x + 1, 0); q.clear();
for (int x = 1; x <= n_x; ++ x)
if (st[x] == x && !match[x])
pa[x] = 0, S[x] = 0, qpush(x);
if (q.empty()) return 0;
while (1) {
while (q.size()) {
int u = q.front(); q.pop_front();
if (S[st[u]] == 1) continue;
for (int v = 1; v <= n; ++v)
if (g[u][v].w > 0 && st[u] != st[v]) {
if (dist(g[u][v]) == 0) {
if (onFoundedge(g[u][v]))
return 1;
}
else update(u, st[v]);
}
}
int d = inf;
for (int b = n + 1; b <= n_x; ++ b)
if (st[b] == b && S[b] == 1)
d = min(d, lab[b] / 2);
for (int x = 1; x <= n_x; ++ x)
if (st[x] == x && slk[x]) {
if (S[x] == -1) d = min(d, dist(g[slk[x]][x]));
else if (S[x] == 0) d = min(d, dist(g[slk[x]][x]) / 2);
}
for (int u = 1; u <= n; ++ u) {
if (S[st[u]] == 0) {
if (lab[u] <= d) return 0;
lab[u] -= d;
}
else if (S[st[u]] == 1) lab[u] += d;
}
for (int b = n + 1; b <= n_x; ++ b)
if (st[b] == b) {
if (S[st[b]] == 0) lab[b] += d * 2;
else if (S[st[b]] == 1) lab[b] -= d * 2;
}
q.clear();
for (int x = 1; x <= n_x; ++ x)
if (st[x] == x && slk[x] && st[slk[x]] != x && dist(g[slk[x]][x]) == 0)
if (onFoundedge(g[slk[x]][x])) return 1;
for (int b = n + 1; b <= n_x; ++ b)
if (st[b] == b && S[b] == 1 && lab[b] == 0)
expandBlossom(b);
}
return 0;
}
pair<ll, int> weightBlossom() {
fill (match, match + n + 1, 0);
n_x = n; int n_matches = 0, w_max = 0;
ll tot_weight = 0;
for (int u = 0; u <= n; ++ u) st[u] = u, flower[u].clear();
for (int u = 1; u <= n; ++ u)
for (int v = 1; v <= n; ++ v) {
ff[u][v] = (u == v ? u : 0);
w_max = max(w_max, g[u][v].w);
}
for (int u = 1; u <= n; ++ u) lab[u] = w_max;
while (matching()) n_matches ++;
for (int u = 1; u <= n; ++ u)
if (match[u] && match[u] < u)
tot_weight += g[u][match[u]].w;
return make_pair(tot_weight, n_matches);
}
int main() {
cin >> n >> m;
for(int u = 1; u <= n; ++ u)
for(int v = 1; v <= n; ++ v)
g[u][v] = edge{u, v, 0};
for(int u, v, w; m --; ) {
cin >> u >> v >> w;
g[u][v].w = g[v][u].w = w;
}
cout << weightBlossom().first << '\n';
for (int u = 1; u <= n; ++ u) cout << match[u] << ' ';
}
10.网络流
最大流:
我们有一张图,要求从源点流向汇点的最大流量(可以有很多条路到达汇点),就是我们的最大流问题。
最小费用最大流:
最小费用最大流问题是这样的:每条边都有一个费用,代表单位流量流过这条边的开销。我们要在求出最大流的同时,要求花费的费用最小。
10.1 Dinic算法求网络最大流
多路增广+当前弧优化,跑的飞快, 时间复杂度 O ( N 2 ∗ M ) O(N^2*M) O(N2∗M)
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=1;i<=n;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
int u,v,w;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
}
printf("%d",dinic());
return 0;
}
10.2 类Dinic算法-SPFA费用流
多路增广的费用流,时间复杂度 O ( N ∗ M ∗ F ) O(N*M*F) O(N∗M∗F)
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f,maxn=5e3+5,maxm=5e4+5;
int n,m,s,t,cost,maxflow,vis[maxn],dis[maxn];
struct edge{
int v,flow,cost,rev;
};
vector<edge>e[maxm*2];
inline void add_edge(int u,int v,int flow,int cost){
e[u].push_back((edge){v,flow,cost,e[v].size()});
e[v].push_back((edge){u,0,-cost,e[u].size()-1});
}
bool spfa(){
memset(vis,0,sizeof(vis));
memset(dis,inf,sizeof(dis));
dis[s]=0;
vis[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=0;i<e[u].size();i++){
int v=e[u][i].v;
int c=e[u][i].cost;
if(dis[v]>dis[u]+c&&e[u][i].flow){
dis[v]=dis[u]+c;
if(vis[v]==0){
q.push(v);
vis[v]=1;
}
}
}
}
if(dis[t]!=inf)return true;
return false;
}
int dfs(int u,int flow){
if(u==t){
vis[t]=1;
maxflow+=flow;
return flow;
}
int used=0;
vis[u]=1;
for(int i=0;i<e[u].size();i++){
int v=e[u][i].v;
int c=e[u][i].cost;
if((vis[v]==0||v==t)&&e[u][i].flow!=0&&dis[v]==dis[u]+c){
int minflow=dfs(v,min(flow-used,e[u][i].flow));
if(minflow!=0){
cost+=c*minflow;
e[u][i].flow-=minflow;
e[v][e[u][i].rev].flow+=minflow;
used+=minflow;
if(used==flow)break;
}
}
}
return used;
}
int mcmf(){
while(spfa()){
vis[t]=1;
while(vis[t]){
memset(vis,0,sizeof(vis));
dfs(s,inf);
}
}
return maxflow;
}
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
int u,v,f,w;
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&u,&v,&f,&w);
add_edge(u,v,f,w);
}
mcmf();
printf("%d %d",maxflow,cost);
return 0;
}
10.3 EK费用流
效率没有上一种费用流高,但是优点在于可以记录每次增广的费用和流量,方便动态操作
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=0x3f3f3f3f,maxn=1005,maxm=100005;
struct edge{
int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m;
inline void add_edge(int u,int v,int cost,int flow){
e[++cnt].v=v;
e[cnt].cost=cost;
e[cnt].flow=flow;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].cost=-cost;
e[cnt].flow=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
int inque[maxn];
int dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
queue<int>q;
memset(dis,0x3f,sizeof dis);
memset(flow,0x3f,sizeof flow);
memset(inque,0,sizeof inque);
q.push(s);
inque[s]=1;
dis[s]=0;
pre[t]=-1;
while(!q.empty()){
int u=q.front();
q.pop();
inque[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
dis[v]=dis[u]+e[i].cost;
pre[v]=u;
last[v]=i;
flow[v]=min(flow[u],e[i].flow);
if(!inque[v]){
q.push(v);
inque[v]=1;
}
}
}
}
if(pre[t]!=-1)return true;
return false;
}
int maxflow,mincost;
void mcmf(int s,int t){
maxflow=0;
mincost=0;
while(spfa(s,t)){
int u=t;
// printf("maxflow==%d %d %d\n",maxflow,flow[t],flow[t]*dis[t]);
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(u!=s){
e[last[u]].flow-=flow[t];
e[last[u]^1].flow+=flow[t];
u=pre[u];
}
}
return;
}
10.4上下界网络流
咕 咕 咕 待补
11.2-SAT问题
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=2e6+5,inf=0x3f3f3f3f;
int dfn[maxn],z[maxn],vis[maxn],low[maxn],tot,top,t,color[maxn],cnt[maxn];
vector<int>e[maxn];
int n,m;
inline void add(int u,int v){
e[u].emplace_back(v);
}
void tarjan(int u){
dfn[u]=low[u]=++tot;
z[++top]=u;//入栈
vis[u]=1;
for(auto v:e[u]){
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
t++;//连通分量的标号
do{
color[z[top]]=t; //属于这个连通分量
cnt[t]++; //记录这个环中有多少个点
vis[z[top]]=0;
top--; //出栈
}while(u!=z[top+1]);
}
}
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y,a,b;
scanf("%d%d%d%d",&x,&a,&y,&b);
if(a&&b) add(x+n,y), add(y+n,x);
if(!a&&b) add(x,y), add(y+n,x+n);
if(a&&!b) add(x+n,y+n),add(y,x);
if(!a&&!b) add(x,y+n), add(y,x+n);
}
for(int i=1;i<=2*n;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++){
if(color[i]==color[i+n]){
puts("IMPOSSIBLE");
return 0;
}
}
puts("POSSIBLE");
for(int i=1;i<=n;i++){
printf("%d%c",color[i]<color[i+n],i==n?'\n':' '); //Tarjan 求得的强连通分量的标号为拓扑逆序,即反向的拓扑序
}
return 0;
}
12.杂项
12.1图的染色
12.2欧拉图
定义¶
通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路。
通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路。
具有欧拉回路的无向图或有向图称为欧拉图。
具有欧拉通路但不具有欧拉回路的无向图或有向图称为半欧拉图。
有向图也可以有类似的定义。
非形式化地讲,欧拉图就是从任意一个点开始都可以一笔画完整个图,半欧拉图必须从某个点开始才能一笔画完整个图。
12.3哈密顿图
定义¶
通过图中所有顶点一次且仅一次的通路称为哈密顿通路。
通过图中所有顶点一次且仅一次的回路称为哈密顿回路。
具有哈密顿回路的图称为哈密顿图。
具有哈密顿通路而不具有哈密顿回路的图称为半哈密顿图。
12.4拆点
网络流题目中的常见处理点限制方法
12.5判环
在 J o h n s o n Johnson Johnson全源最短路的过程中就用到了这一段代码
在跑 s p f a spfa spfa的过程中,若有点的 入队次数 > = n >=n >=n 即有负环
bool spfa(int s){
queue<int>q;
memset(h,63,sizeof(h));
h[s]=0,vis[s]=1;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(h[v]>h[u]+e[i].w){
h[v]=h[u]+e[i].w;
if(!vis[v]){
vis[v]=1;
q.push(v);
cs[v]++;
if(cs[v]==n)return false;
}
}
}
}
return true;
}
12.6 无向图的最小环
#include<bits/stdc++.h>
using namespace std;
constexpr int maxn=105,inf=0x3f3f3f3f;
int mp[maxn][maxn],dis[maxn][maxn];
int n,m;
int main(){
cin>>n>>m;
memset(mp,inf,sizeof mp);
memset(dis,inf,sizeof dis);
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
mp[u][v]=mp[v][u]=w;
mp[u][v]=mp[v][u]=dis[u][v]=dis[v][u]=min(dis[u][v],w);
}
int ans=inf;
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
ans=(int)min((long long)ans,(long long)dis[i][j]+mp[j][k]+mp[k][i]);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
if(ans!=inf)cout<<ans;
else puts("No solution.");
}
13.更高级的图论
你是否和当年萌新的我一样以为网络流就是图论最难的内容了?
13.1 弦图
13.2 圆方树
13.3 斯坦纳树
13.4 动态树
1.Link Cut Tree
2.Euler Tour Tree
3.Top Tree
13.5 图论终极大BOSS-动态仙人掌
如果一张无向连通图的每条边最多在一个环内,则称它是一棵 仙人掌 (Cactus) 。多棵仙人掌可以组成沙漠 。
模板题- UOJ-动态仙人掌