割点和桥小结
桥
定义
无向连通图中如果一条边断开后能使图不连通则条边就是桥。
方法
dfs+并查集
每条边只能走一次。如果搜到一个点还在访问中说明他们是双连通分量,用并查集合并。
Code
void dfs(int x)
{
for(int g=h[x]; g; g=d[g].n)
{
if(c[g]) continue;
c[((g-1)^1)+1]=c[((g-1)^1)+1]=1;
int y=d[g].y;
if(!dep[y])
{
dep[y]=dep[x]+1;
dfs(y);
}
if(dep[fa(y)]>0)
{
if(dep[f[y]]<dep[fa(x)]) f[f[x]]=f[y];
else f[f[y]]=f[x];
}
}
dep[x]=-1;
}
割点
定义
在一个有向联通图中,如果一个点删去后使这个图不连通,那么这个点就是这个图的割点。
方法
我们记当前这个点最早能返回到的祖先为 \(f_i\),当我们搜到一个点时,如果这个点还没有被搜,我们就搜他。最后如果这个点的 \(f_j\) 深度比 \(i\) 还深,说明 \(i\) 多了个儿子节点。然后如果 \(f_j\) 比 \(f_i\) 浅,我们用 \(f_j\) 更新 \(f_i\)。
如果这个点已经被搜到过,我们就有 \(j\) 来更新 \(f_i\)。
Code
void dfs(int x)
{
for(int g=h[x]; g; g=d[g].n)
{
int y=d[g].y;
if(!c[y])
{
c[y]=c[x]+1;
dfs(y);
if(c[f[y]]<c[f[x]]) f[x]=f[y];
if(c[f[y]]>=c[x]) ++zi[x];
}
else if(c[y]<c[f[x]]) f[x]=y;
}
}
例题
Loj #10098. 「一本通 3.6 例 1」分离的路径
首先,环内的节点必然可以至少存在两条路径到达,所以我们不用考虑环内的节点,可以先对无向图缩点。
剩下的节点必然构成一棵树,我们只需要将叶子节点两两配对。因为这样其上面的所有父亲节点都可以通过它下面的叶子节点形成环。
Code
// Problem: #10098. 「一本通 3.6 例 1」分离的路径
// Contest: LibreOJ
// URL: https://loj.ac/p/10098
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define M 10010
//#define mo
#define N 5010
struct node
{
int x, y, n;
}d[M*2];
int n, m, i, j, k;
int h[N], f[N], c[N], dep[N], rd[N];
int lf, u, v;
void cun(int x, int y)
{
d[++k].x=x; d[k].y=y;
d[k].n=h[x]; h[x]=k;
}
int fa(int x)
{
if(f[x]==x) return x;
return f[x]=fa(f[x]);
}
void dfs(int x)
{
for(int g=h[x]; g; g=d[g].n)
{
if(c[g]) continue;
c[((g-1)^1)+1]=c[((g-1)^1)+1]=1;
int y=d[g].y;
if(!dep[y])
{
dep[y]=dep[x]+1;
dfs(y);
}
if(dep[fa(y)]>0)
{
if(dep[f[y]]<dep[fa(x)]) f[f[x]]=f[y];
else f[f[y]]=f[x];
}
}
dep[x]=-1;
}
signed main()
{
// freopen("tiaoshi.in", "r", stdin);
// freopen("tiaoshi.out", "w", stdout);
n=read(); m=read();
for(i=1; i<=m; ++i)
{
u=read(); v=read();
cun(u, v); cun(v, u);
}
for(i=1; i<=n; ++i) f[i]=i;
for(i=1; i<=n; ++i)
if(!dep[i])
dep[i]=1, dfs(1);
for(i=1; i<=n; ++i) fa(i);
// for(i=1; i<=n; ++i) printf("%lld ", f[i]);
// printf("\n");
for(i=1; i<=m; ++i)
{
if(f[d[i*2].x]!=f[d[i*2].y])
++rd[f[d[i*2].x]], ++rd[f[d[i*2].y]];
}
for(i=1; i<=n; ++i)
if(f[i]==i&&rd[i]==1) ++lf;
printf("%lld", (lf+1)/2);
return 0;
}
Loj #10099. 「一本通 3.6 例 2」矿场搭建
我们先对于有向图缩点,变成一棵树。
然后我们对于每个树上且在原图中的分割点节点所对应原图中的连通块考虑。
假设这里没有割点,很明显,只需要放2个出口即可。
如果有一个割点,说明这个点是树上的叶子节点,需要放1个出口。
如果有两个或以上的割点,无论哪个割点被割,都可以往另一个方向逃,所以这个连通块不用放。
Code
// Problem: P3225 [HNOI2012]矿场搭建
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3225
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
//#define mo
#define M 510
#define N 1010
struct node
{
int x, y, n;
}d[M*2];
int n, m, i, j, k, T;
int h[N], dep[N], c[N], w[N], f[N], zi[N];
int u, v, ansx, ansy, a, b;
void cun(int x, int y)
{
n=max(n, x);
d[++k].x=x; d[k].y=y;
d[k].n=h[x]; h[x]=k;
}
void dfs(int x)
{
for(int g=h[x]; g; g=d[g].n)
{
int y=d[g].y;
if(!dep[y])
{
dep[y]=dep[x]+1;
dfs(y);
if(dep[f[y]]>=dep[x]) ++zi[x];
if(dep[f[y]]<dep[f[x]]) f[x]=f[y];
}
else if(dep[y]<dep[f[x]]) f[x]=y;
}
}
void foofill(int x)
{
for(int g=h[x]; g; g=d[g].n)
{
int y=d[g].y;
if(c[y]<j)
{
c[y]=j;
if(w[y]) ++b;
else ++a, foofill(y);
}
}
}
void init()
{
n=m=i=j=k=u=v=ansx=ansy=a=b=0;
memset(h, 0, sizeof(h));
memset(dep, 0, sizeof(dep));
memset(c, 0, sizeof(c));
memset(w, 0, sizeof(w));
memset(f, 0, sizeof(f));
memset(zi, 0, sizeof(zi));
}
signed main()
{
// freopen("tiaoshi.in","r",stdin);
// freopen("tiaoshi.out","w",stdout);
while(1)
{
init();
m=read();
if(!m) return 0;
printf("Case %lld: ", ++T);
// printf("\n");
for(i=1; i<=m; ++i)
{
u=read(); v=read();
cun(u, v); cun(v, u);
}
// printf("%lld ", n);
for(i=1; i<=n; ++i) f[i]=i;
for(i=1; i<=n; ++i)
if(!dep[i])
dep[i]=1, dfs(i);
// for(i=1; i<=n; ++i)
// printf("%lld %lld %lld\n", i, f[i], zi[i]);
for(i=1; i<=n; ++i)
if((dep[i]!=1&&zi[i])||(zi[i]>=2))
w[i]=1;
// for(i=1; i<=n; ++i) printf("%lld ", w[i]);
// printf("-------------------\n");
ansy=1;
for(i=1, j=0; i<=n; ++i)
if(c[i]==0&&w[i]==0&&h[i])
{
c[i]=(++j);
b=0; a=1;
foofill(i);
if(b==0)
{
if(a==1) ++ansx;
else ansx+=2, ansy*=(a*(a-1)/2);
}
else if(b==1) ++ansx, ansy*=a;
// printf("%lld %lld %lld %lld\n", i, a, b, j);
}
printf("%lld %lld\n", ansx, ansy);
}
return 0;
}
Loj #10100. 「一本通 3.6 练习 1」网络
题目就是给出一幅图,求其割点个数。
由于 \(n\leqslant 100\),所以可以暴力删点。
当然也可以跑割点。
(感谢crx老师教我割点模板)
暴力Code
// Problem: #10100. 「一本通 3.6 练习 1」网络
// Contest: LibreOJ
// URL: https://loj.ac/p/10100
// Memory Limit: 10 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
//#define mo
#define N 110
struct node
{
int x, y, n;
}d[N*N*2];
int n, m, i, j, k;
int x, y, z, ans;
int h[N], c[N];
char ch;
void cun(int x, int y)
{
d[++k].x=x; d[k].y=y;
d[k].n=h[x]; h[x]=k;
}
void init()
{
m=i=j=k=x=y=z=ans=ch=0;
memset(h, 0, sizeof(h));
memset(c, 0, sizeof(c));
memset(d, 0, sizeof(d));
}
void dfs(int x)
{
for(int g=h[x]; g; g=d[g].n)
{
int y=d[g].y;
if(c[y]) continue;
c[y]=1;
dfs(y);
}
}
signed main()
{
// freopen("tiaoshi.in","r",stdin);
// freopen("tiaoshi.out","w",stdout);
n=read();
while(n)
{
init();
scanf("%d", &i);
while(i)
{
scanf("%d%c", &j, &ch);
// printf("%d %d\n", i, j);
cun(i, j); cun(j, i);
while(ch>=32)
{
scanf("%d%c", &j, &ch);
// printf("%d %d\n", i, j);
cun(i, j); cun(j, i);
}
scanf("%d", &i);
// printf("%d\n", i);
}
i=k=0;
for(j=1; j<=n; ++j)
if(!c[j]) c[j]=1, dfs(j), ++k;
// printf("k:%d\n", k);
for(i=1; i<=n; ++i)
{
memset(c, 0, sizeof(c));
c[i]=1; m=0;
for(j=1; j<=n; ++j)
if(!c[j]) c[j]=1, dfs(j), ++m;
if(m!=k) ++ans;
// printf("m:%d\n", m);
}
printf("%d\n", ans);
n=read();
}
return 0;
}
割点Code
// Problem: #10100. 「一本通 3.6 练习 1」网络
// Contest: LibreOJ
// URL: https://loj.ac/p/10100
// Memory Limit: 10 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
//#define mo
#define N 110
struct node
{
int x, y, n;
}d[N*N*2];
int n, m, i, j, k;
int x, y, z, ans;
int h[N], c[N], f[N], zi[N];
char ch;
void cun(int x, int y)
{
d[++k].x=x; d[k].y=y;
d[k].n=h[x]; h[x]=k;
}
void init()
{
m=i=j=k=x=y=z=ans=ch=0;
memset(h, 0, sizeof(h));
memset(c, 0, sizeof(c));
memset(d, 0, sizeof(d));
memset(f, 0, sizeof(f));
memset(zi, 0, sizeof(zi));
}
void dfs(int x)
{
for(int g=h[x]; g; g=d[g].n)
{
int y=d[g].y;
if(!c[y])
{
c[y]=c[x]+1;
dfs(y);
if(c[f[y]]<c[f[x]]) f[x]=f[y];
if(c[f[y]]>=c[x]) ++zi[x];
}
else if(c[y]<c[f[x]]) f[x]=y;
}
}
signed main()
{
// freopen("tiaoshi.in","r",stdin);
// freopen("tiaoshi.out","w",stdout);
n=read();
while(n)
{
init();
scanf("%d", &i);
while(i)
{
scanf("%d%c", &j, &ch);
// printf("%d %d\n", i, j);
cun(i, j); cun(j, i);
while(ch>=32)
{
scanf("%d%c", &j, &ch);
// printf("%d %d\n", i, j);
cun(i, j); cun(j, i);
}
scanf("%d", &i);
// printf("%d\n", i);
}
for(i=1; i<=n; ++i) f[i]=i;
for(i=1; i<=n; ++i)
if(!c[i]) c[i]=1, dfs(i);
for(i=1; i<=n; ++i)
if((zi[i]>1)||(f[i]!=i&&zi[i])) ++ans;
printf("%d\n", ans);
n=read();
}
return 0;
}
Loj #10101. 「一本通 3.6 练习 2」嗅探器
首先如果一个点满足答案,则这个点一定是割点。
然后我们可以从 \(a\) 点开始搜,对于每一个点,如果 \(b\) 点在它的儿子内,说明这个点分离了 \(a\) 和 \(b\)。
如何判断 \(b\) 是否在它的儿子内,只需要在搜索这个儿子前后判断一下即可。
Code
// Problem: P5058 [ZJOI2004]嗅探器
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5058
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define M 500010
//#define mo
#define N 200010
struct node
{
int x, y, n;
}d[M*2];
int n, m, i, j, k;
int dep[N], f[N], h[N], zi[N];
int u, v, ans, a, b;
void cun(int x, int y)
{
d[++k].x=x; d[k].y=y;
d[k].n=h[x]; h[x]=k;
}
void dfs(int x)
{
int flag=0;
for(int g=h[x]; g; g=d[g].n)
{
int y=d[g].y;
if(!dep[y])
{
int flg=(dep[b]!=0 ? 0 : 1);
dep[y]=dep[x]+1;
dfs(y);
if(dep[f[y]]>=dep[x])
{
++zi[x];
if(dep[b]) flag+=flg;
}
if(dep[f[y]]<dep[f[x]]) f[x]=f[y];
}
else if(dep[y]<dep[f[x]]) f[x]=y;
}
// printf("%lld: %lld %lld %lld\n", x, f[x], zi[x], flag);
if(x==b||x==a) flag=0;
if(!(zi[x]>1||(x!=a&&zi[x]))) flag=0;
if(flag)
{
ans=min(ans, x);
// printf("%lld\n", x);
}
}
signed main()
{
// freopen("tiaoshi.in", "r", stdin);
// freopen("tiaoshi.out", "w", stdout);
n=read(); ans=0x3f3f3f3f;
for(i=1; i<=n; ++i) f[i]=i;
u=read(); v=read();
while(u) cun(u, v), cun(v, u), u=read(), v=read();
a=read(); b=read();
dep[a]=1; dfs(a);
// for(i=1; i<=n; ++i) printf("%lld ", f[i]);
if(ans==0x3f3f3f3f) printf("No solution");
else printf("%lld", ans);
return 0;
}
Loj #10102. 「一本通 3.6 练习 3」旅游航道
题目中对主要航道定义是这样的:
如果某一条航道的删除使得一些星球不能到达,那么这条航道是不能删除的,称之为「主要航道」。
这说明了什么?
说明了主要航道就是桥。
然后题目就是求桥的个数。
模板题。
Code
// Problem: #10102. 「一本通 3.6 练习 3」旅游航道
// Contest: LibreOJ
// URL: https://loj.ac/p/10102
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
//#define M
//#define mo
#define N 30010
struct node
{
int x, y, n;
}d[N*2];
int n, m, i, j, k;
int f[N], dep[N], h[N], c[N*2];
int u, v, ans;
int fa(int x)
{
if(f[x]==x) return x;
return f[x]=fa(f[x]);
}
void cun(int x, int y)
{
d[++k].x=x; d[k].y=y;
d[k].n=h[x]; h[x]=k;
}
void dfs(int x)
{
for(int g=h[x]; g; g=d[g].n)
{
if(c[g]) continue;
c[g]=c[((g-1)^1)+1]=1;
int y=d[g].y;
if(!dep[y])
{
dep[y]=dep[x]+1;
dfs(y);
}
if(dep[fa(y)]>=0)
{
if(dep[f[y]]<dep[fa(x)]) f[f[x]]=f[y];
else f[f[y]]=f[x];
}
}
dep[x]=-1;
}
void init()
{
memset(f, 0, sizeof(f));
memset(dep, 0, sizeof(dep));
memset(h, 0, sizeof(h));
memset(d, 0, sizeof(d));
memset(c, 0, sizeof(c));
i=j=k=u=v=ans=0;
}
signed main()
{
// freopen("tiaoshi.in", "r", stdin);
// freopen("tiaoshi.out", "w", stdout);
n=read(); m=read();
while(n)
{
init();
for(i=1; i<=n; ++i) f[i]=i;
for(i=1; i<=m; ++i)
u=read(), v=read(), cun(u, v), cun(v, u);
dep[1]=1; dfs(1);
for(i=1; i<=n; ++i) fa(i);
for(i=1; i<=m; ++i)
if(f[d[i*2].x]!=f[d[i*2].y]) ++ans;
printf("%lld\n", ans);
n=read(); m=read();
}
return 0;
}
Loj #10103. 「一本通 3.6 练习 4」电力
首先考虑删走一个点后能增加联通块数量,则这个点一定是割点。
然后就完了啊
tarjan完(虽然我没有打tarjan)我们就分别判断每个点是不是割点。如果是看一下是否有父。统计一下即可。
要注意题目一定要割,所以如果有 \(n\) 个联通块要输出 \(n-1\)。
Code
// Problem: #10103. 「一本通 3.6 练习 4」电力
// Contest: LibreOJ
// URL: https://loj.ac/p/10103
// Memory Limit: 64 MB
// Time Limit: 5000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
//#define mo
#define N 10010
struct node
{
int x, y, n;
}d[N*200];
int n, m, i, j, k;
int u, v, h[N], ans;
int dep[N], zi[N], zao[N];
void cun(int x, int y)
{
d[++k].x=x; d[k].y=y;
d[k].n=h[x]; h[x]=k;
}
void dfs(int x)
{
for(int g=h[x]; g; g=d[g].n)
{
int y=d[g].y;
if(!dep[y])
{
dep[y]=dep[x]+1;
dfs(y);
if(dep[zao[y]]>=dep[x]) ++zi[x];
if(dep[zao[y]]<dep[zao[x]]) zao[x]=zao[y];
}
else if(dep[y]<dep[zao[x]]) zao[x]=y;
}
}
void init()
{
n=m=i=j=k=u=v=ans=0;
memset(h, 0, sizeof(h));
memset(zi, 0, sizeof(zi));
memset(zao, 0, sizeof(zao));
memset(dep, 0, sizeof(dep));
}
signed main()
{
// freopen("tiaoshi.in","r",stdin);
// freopen("tiaoshi.out","w",stdout);
while(1)
{
init();
n=read(); m=read();
if(!n) return 0;
for(i=1; i<=m; ++i)
{
u=read()+1, v=read()+1;
cun(u, v); cun(v, u);
}
for(i=1; i<=n; ++i) zao[i]=i;
for(i=1, k=0; i<=n; ++i)
if(!dep[i])
dep[i]=1, dfs(i), ++k;
ans=1;
for(i=1; i<=n; ++i)
{
if(dep[i]>1&&zi[i]) ans=max(ans, zi[i]+1);
else ans=max(ans, zi[i]);
}
printf("%lld\n", (long long)(k==n ? n-1 : k+ans-1));
}
return 0;
}
Loj #10104. 「一本通 3.6 练习 5」Blockade
首先这个点删去之后必然与剩下 \(n-1\) 个点失去相连。
如果这个点能使其它点失去相连,说明这个点为割点。
然后统计一下每个儿子与父亲的影响即可。
Code
// Problem: #10104. 「一本通 3.6 练习 5」Blockade
// Contest: LibreOJ
// URL: https://loj.ac/p/10104
// Memory Limit: 162 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define M 500010
//#define mo
#define N 100010
struct node
{
int x, y, n;
}d[M*2];
int n, m, i, j, k, u, v;
int h[N], dep[N], f[N], zi[N], s[N], ans[N];
void cun(int x, int y)
{
d[++k].x=x; d[k].y=y;
d[k].n=h[x]; h[x]=k;
}
void dfs(int x)
{
int sum=0;
s[x]=1;
for(int g=h[x]; g; g=d[g].n)
{
int y=d[g].y;
if(!dep[y])
{
dep[y]=dep[x]+1;
dfs(y);
s[x]+=s[y];
if(dep[f[y]]>=dep[x])
{
++zi[x];
ans[x]+=sum*s[y];
sum+=s[y];
}
if(dep[f[y]]<dep[f[x]]) f[x]=f[y];
}
else if(dep[y]<dep[f[x]]) f[x]=y;
}
ans[x]+=sum*(n-1-sum);
ans[x]+=(n-1);
}
signed main()
{
// freopen("tiaoshi.in", "r", stdin);
// freopen("tiaoshi.out", "w", stdout);
n=read(); m=read();
for(i=1; i<=m; ++i)
{
u=read(); v=read();
cun(u, v); cun(v, u);
}
for(i=1; i<=n; ++i) f[i]=i;
dep[1]=1;
dfs(1);
// for(i=1; i<=n; ++i) printf("%lld %lld %lld\n", zi[i], s[i], ans[i]);
for(i=1; i<=n; ++i)
{
if((i!=1&&zi[i])||(zi[i]>=2)) printf("%lld\n", ans[i]*2);
else printf("%lld\n", 2*(n-1));
}
return 0;
}
本文来自博客园,作者:zhangtingxi,转载请注明原文链接:https://www.cnblogs.com/zhangtingxi/p/15631062.html