zoukankan      html  css  js  c++  java
  • 无向图必经点、必经边的相关问题

    无向图必经点、必经边的相关问题

    一、 任意两点间路径的必经边

    模板

    首先考虑到必经边一定是原图的一条割边。

    那么对于一个(e-DCC)中的点是不存在必经边的。不懂(e-DCC)相关内容?戳Here

    那么很容易想到对于每一个(e-DCC)缩点,得到一棵树。两点间路径的必经边条数就是两点所在的

    (e-DCC)对应的新节点的树上路径长度。直接树上倍增即可。

    #include<bits/stdc++.h>
    #define LL long long
    #define DB double
    #define RG register
    #define IL inline
    #define pb push_back
    using namespace std;
    
    const int N=2e5+3;
    
    int n,m,Q,tot,cnt,num,Time,dfn[N],low[N];
    int bri[N<<1],vis[N],bel[N],pos[N],dep[N],head[N],f[N][21];
    
    struct edge{int x,y;}a[N];
    struct EDGE{int next,to;}e[N<<1];
    
    IL int gi() {
        RG int x=0,p=1; RG char ch=getchar();
        while(ch<'0'||ch>'9') {if(ch=='-') p=-1;ch=getchar();}
        while(ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
        return x*p;
    }
    
    IL void New() {
    	tot=0;
    	memset(&e,0,sizeof(e));
    	memset(head,0,sizeof(head));
    	memset(vis,0,sizeof(vis));
    }
    
    IL void make(int x,int y) {
    	e[++tot]=(EDGE){head[x],y},head[x]=tot;
    	e[++tot]=(EDGE){head[y],x},head[y]=tot;
    }
    
    void Tarjan(int x,int fx) {
    	RG int i,y;
    	dfn[x]=low[x]=++Time;
    	for(i=head[x];i;i=e[i].next)
    		if(!dfn[y=e[i].to]) {
    			Tarjan(y,x),low[x]=min(low[x],low[y]);
    			if(low[y]>dfn[x]) bri[i]=bri[i^1]=1;
    		}
    		else if(y!=fx) low[x]=min(low[x],dfn[y]);
    }
    
    void dfs(int x) {
    	RG int i,y;
    	vis[x]=1,bel[x]=cnt;
    	for(i=head[x];i;i=e[i].next)
    		if(!vis[y=e[i].to]&&!bri[i]) dfs(y);
    }
    
    void dfs2(int x) {
    	RG int i,y;
    	pos[x]=num,vis[x]=1;
    	for(i=1;i<=20;++i) f[x][i]=f[f[x][i-1]][i-1];
    	for(i=head[x];i;i=e[i].next)
    		if(!vis[y=e[i].to])
    			dep[y]=dep[x]+1,f[y][0]=x,dfs2(y);
    }
    
    IL int lca(int x,int y) {
    	RG int i; 
    	if(dep[x]<dep[y]) swap(x,y);
    	for(i=20;i>=0;--i)
    		if(dep[f[x][i]]>=dep[y]) x=f[x][i];
    	if(x==y) return x;
    	for(i=20;i>=0;--i)
    		if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    	return f[x][0];
    }
    
    int main()
    {
    	RG int i,x,y;
    	n=gi(),m=gi(),tot=1;
    	for(i=1;i<=m;++i)
    		a[i].x=gi(),a[i].y=gi(),make(a[i].x,a[i].y);
    	for(i=1;i<=n;++i)
    		if(!dfn[i]) Tarjan(i,0);
    	for(i=1;i<=n;++i)
    		if(!vis[i]) ++cnt,dfs(i);
    	for(i=1,New();i<=m;++i) {
    		x=a[i].x,y=a[i].y;
    		if(bel[x]!=bel[y]) make(bel[x],bel[y]);
    	}
    	for(i=1;i<=cnt;++i)
    		if(!vis[i]) f[i][0]=0,dep[i]=1,dfs2(i);
    	for(Q=gi();Q;--Q) {
    		x=bel[gi()],y=bel[gi()];
    		if(pos[x]!=pos[y]) puts("0"); // 是否在同一连通块
    		else {
    			if(x==y) puts("0"); // 是否在同一e-DCC
    			else printf("%d
    ",dep[x]+dep[y]-2*dep[lca(x,y)]);
    		}
    	}
    	return 0;
    }
    
    

    二、 任意两点间路径的必经点

    同上面一样,必经点一定是割点。位于同一个(v-DCC)中的两点间路径不存在必经点。

    同样的进行(v-DCC)缩点。这时(v-DCC)缩点方式的优越体现了出来:

    假设每一个(v-DCC)缩完的新点叫圆点,每一个割点对应的新点叫方点,

    那么两点间路径的必经点个数就是 两点所对应的树上新点 间路径上的方点个数!

    事实上,这也正体现了(v-DCC)缩点后原来的点的性质在新图中的分布特点:

    割点的性质体现在割点对应的新点(方点)上;

    非割点的性质体现在其所在(v-DCC)对应的新点(圆点)上。

    void tarjan(int x) {
    	RG int i,k,y,num=0;
    	dfn[x]=low[x]=++Time,sta[++top]=x;
    	if(x==RT&&head[x]==0) {
    		vec[++cnt].push_back(x);
    		return;
    	}   // 孤立点的情况
    	for(i=head[x];i;i=e[i].next)
    		if(!dfn[y=e[i].to]) {
    			tarjan(y),low[x]=min(low[x],low[y]);
    			if(dfn[x]<=low[y]) {
    				if(x!=RT||++num>1) cut[x]=1;
    				vec[++cnt].push_back(x);
    				do{
    					k=sta[top--]; 
    					vec[cnt].push_back(k);
    				}while(k!=y);
    			}
    		}
    		else low[x]=min(low[x],dfn[y]);
    }
    
    void dfs(int x,int fx) {
    	RG int i,y;
    	dep[x]=dep[fx]+1,f[x][0]=fx,g[x][0]=mtx[fx],vis[x]=1;
      	// g[][]记录的是往上跳的方点个数
    	for(i=1;i<=20;++i)
    		f[x][i]=f[f[x][i-1]][i-1],g[x][i]=g[x][i-1]+g[f[x][i-1]][i-1];		
    	for(i=head[x];i;i=e[i].next)
    		if((y=e[i].to)!=fx) dfs(y,x);
    }
    
    IL int lca(int x,int y) {
    	RG int i,dis=0;
    	if(dep[x]<dep[y]) swap(x,y);
    	for(i=20;i>=0;--i)
    		if(dep[f[x][i]]>=dep[y]) dis+=g[x][i],x=f[x][i];
    	if(x==y) return dis-mtx[y];
    	for(i=20;i>=0;--i)
    		if(f[x][i]!=f[y][i]) dis+=g[x][i]+g[y][i],x=f[x][i],y=f[y][i];
    	return dis+g[x][0]+g[y][0]-mtx[f[x][0]];
    }
    
    int main()
    {
    	while(scanf("%d%d",&n,&m)!=EOF&&n&&m) {
    		New_case();
    		RG int i,j,x,y;
    		for(i=1;i<=m;++i)
    			scanf("%d%d",&s[i].x,&s[i].y),make(s[i].x,s[i].y);
    		for(i=1;i<=n;++i)
    			if(!dfn[i]) RT=i,tarjan(i);
    		for(i=1;i<=n;++i)
    			if(cut[i]) bel[i]=++cnt,mtx[cnt]=1;
          	//mtx[]记录的是缩点后树上的点是不是方点。
    		for(i=1,New();i<=cnt;++i)
    			for(j=0;j<vec[i].size();++j)
    				if(cut[x=vec[i][j]]) make(i,bel[x]);
    				else bel[x]=i;
    		for(i=1;i<=cnt;++i)
    			if(!vis[i]) dfs(i,0);
    		for(scanf("%d",&Q);Q;--Q) {
    			scanf("%d%d",&x,&y);
    			if(bel[x]==bel[y]) puts("0");
    			else printf("%d
    ",lca(bel[x],bel[y]));
    		}
    	}
    	return 0;
    }
    // 无向图两点间路径的必经点
    

    上面两个问题事实上都涉及到点的性质在双连通分量中的体现,而下面问题则涉及到边的性质在双连通分量中的体现。

    三、 任意两边间路径的必经点

    模板

    简单来讲,问题需要解决的只有边的归属问题(边丢在哪一个新点中),然后用同上的方法即可解决。

    这个时候分类讨论一下:

    1、边两端点都是非割点。那么这两点必然在一个(v-DCC)中,该边就对应该(v-DCC)对应的节点(圆点)。

    2、边两端点有且仅有一个割点。这样的边对应 其非割点端点所在(v-DCC)对应的节点(圆点)。

    3、边两端点都是割点。这样的边就对应 仅包含这两个割点的(v-DCC)对应的节点(圆点)。

    ​ 这个具体讲一下,因为两点都为割点,那么先遍历到二者之一后,必然会把另外一个点和该点加入一个新的(v-DCC)

    ​ 而且该(v-DCC)也仅有这两个点。因此我们可以在划分(v-DCC)的过程中,记录一下点被划分进入(v-DCC)编号

    ​ (不包括最后单独放入的那个节点),然后对于这两割点,比较一下谁先被划分,就丢入它对应的(v-DCC)即可。

    会抓紧补上一张图的。。。

    Tarjan 中的记录 :

    if(dfn[x]<=low[y]) {
    	if(x!=RT||++num>1) cut[x]=1;
    		vec[++cnt].push_back(x); // x不考虑
    		do{
    			k=sta[top--]; 
    			vec[cnt].push_back(k);
                in[k]=cnt; // 记录一下编号
            }while(k!=y);
    }
    

    边的归属划分:

    for(i=1;i<=m;++i) {
    	x=s[i].x,y=s[i].y;
    	if(!cut[x]) pos[i]=bel[x];
    	else if(!cut[y]) pos[i]=bel[y];
    	else {
    		if(!in[x]) pos[i]=in[y];
    		else if(!in[y]) pos[i]=in[x];
    		else pos[i]=min(in[x],in[y]);
    	}
    }
    

    然后同样考虑树上路径的方点个数即可。

    四、 任意两边间路径的必经边

    这个的话,边的划分没那么多复杂。

    对于非割边,直接丢在其任一端点对应的(e-DCC)中(两端点在同一(e-DCC))。

    对于割边,新建一个节点,其类似于上面的割点对应的新节点,也就是方点。

    然后考虑树上路径的方点个数就好了!

    五、声明:

    这一类问题大概讲到这里为止了,如果有什么问题欢迎指出。

    另外,圆点方点跟那个SMG圆方树没啥关系。。。hhh

  • 相关阅读:
    回调函数实现类似QT中信号机制
    Qt Creator下载和安装(详细教程)
    对象池实现分析
    MongoDB Redis
    双重加锁
    开源项目
    进程创建
    WebAPI性能优化
    StatusCodePagesMiddleware中间件如何针对响应码呈现错误页面
    NET Core + Angular 2
  • 原文地址:https://www.cnblogs.com/Bhllx/p/11273294.html
Copyright © 2011-2022 走看看