强联通分量 割点 割边 2-sat问题

树的直径:

两次dfs求。任意一个点的距离最远的一个点总是在直径(s,t)上。

证明:引理:在一个连通无向无环图中, x 、y  和 z 是三个不同的结点。当 x 到 y 的最短路与 y 到 z 的最短路不重合时, x 到 z 的最短路就是这两条最短路的拼接。

定理:在一个连通无向无环图中,以任意结点出发所能到达的最远结点,一定是该图直径的端点之一。

分两种情况:

当出发结点在直径上,当出发节点不在直径上(当最远路与直径相交,或者不相交)画图看一下即可。

强连通分量

主要分为塔杨算法和两次dfs做法(英文不知道怎么读)

附上两个模板。

​
#include<bits/stdc++.h>
using namespace std;
vector<int> a[10000],ra[20000];//提前建好了图 
int ans[100000];
int use[10000];
void dfs(int x)
{
	if(use[x]) return;
	use[x]=1;
	ans[++num]=x; 
	for(int i=0;i<a[x].size();i++)
	{
		if(!use[a[x][i]]) dfs(a[x][i]);
	}
}
void fdfs(int x)
{
	if(use[x]) return;
	use[x]=1;
	hao[x]=shu;//分清谁和谁是一个强连通分量 
	for(int i=1;i<ra[x].size();i++)
	{
		if(!use[ra[x][i]])
		{
			fdfs(ra[x][i]);
		}
	}
}
void solve()
{
	for(int i=1;i<=n;i++)
	if(!use[i]) dfs(i);
	//充值use数组
	for(int i=1;i<=n;i++)//后序遍历,从图的头部开始
	if(!use[ans[i]])
	{
		++shu;//这里标记强连通分量的号码
		fdfs(ans[i]);
	}
	//依据强连通分量的号码后期可以再缩点,就是重新建图嘛 
 } 

​

Tarjan还是很好理解的,用时间戳标记访问没顺便标记访问的时间,当low【u】=dfn【u】是说明有一个强连通分量,当某个点已经在栈中了说明有人提前一步进入

这个连通分量,只能用dfn去更新,非常直观的暴力算法。

#include<bits/stdc++.h>
using namespace std;
vector<int> a[10000];//提前建好了图 
void tayan(int x)
{
	dfn[x]=++num;
	zhan[++index]=x;//入栈 
	vis[x]=1;//标记在栈内
	low[x]=num; 
	for(int i=0;i<a[x].size();i++)
	{
		if(!dfn[a[x][i]])
		{
			tayan(a[x][i]);
			low[x]=min(low[x],low[a[x][i]]);
		}
		else if(vis[a[x][i]])//都不在栈里面了还更新个锤子 
		{
			low[x]=min(low[x],dfn[a[x][i]]);
		}
	}
	if(low[x]==dfn[x]) 
	{
	   ++s;//染色标记 
	   while(zhan[index]!=x)
	   {
	     vis[zhan[index]]=0; 
	   	 scc[zhan[index]]=s;
	   	 index--;
	   }
	   vis[zhan[index]]=0;
	   scc[zhan[index--]]=s;
    }
void solve()
{
  for(int i=1;i<=n;i++)
  if(!dfn[i]) tayan(i);
} 

双连通分量

首先先说割点和割边。

割点的两个充分条件如下:
1、点为根,如果有两个子树则这个点一定是割点。

2、点不为根且不经过这个点无法回到这个点的祖先,这个点是割点。

那么如果是根但是符合2的情况呢,那没得说,因为按照定义,这个点没有祖先,

那么点不为根却又有两个子树呢,那么要么可以返回到祖先的不是割点,不能返回的是割点,所以还是符合2的情况。

1.可以另外判断,2可以是tayan中的low[v]>=dfn[u]

为什么可以等于呢?

因为如果是一个环的话,假若是根,则没有两个子树,假如不是,则可以返回祖先,那么==的作用体现在哪里?还要啊割点和割边据说只差一个=号哦。

#include<bits/stdc++.h>
using namespace std;
vector<int> a[10000];//提前建好了图 
void tayan(int x)
{
	dfn[x]=++num;
	zhan[++index]=x;//入栈 
	vis[x]=1;//标记在栈内
	low[x]=num; 
    int zishu=0;//这是子树无疑了。
	for(int i=0;i<a[x].size();i++)
	{
		if(!dfn[a[x][i]])
		{
            zishu++;
			tayan(a[x][i]);
			low[x]=min(low[x],low[a[x][i]]);
             if((root==x && zishu>1) || (root!=x && dfn[x]<=low[a[x][i]]) gedian[x]=1;
//这里是割点无疑
		}
		else if(vis[a[x][i]])//都不在栈里面了还更新个锤子 
		{
			low[x]=min(low[x],dfn[a[x][i]]);
		}
 
	}
   
	
void solve()
{
  for(int i=1;i<=n;i++)
  if(!dfn[i]) tayan(i);
} 


 

割边:

还是上面那句话,割点和割边在点不为根时候的情况就差一分“=”号而已,这是点和边性质的不同,不为根的点中经过某条边如果能回到这个点说明可以是一个环

(或者是强连通分量),那么这个点可以是割点,却不能成为割边,因为割掉了也不影响连通性。

#include<bits/stdc++.h>
using namespace std;
vector<int> a[10000];//提前建好了图 
void tayan(int x,int fa)
{
	dfn[x]=++num;
	zhan[++index]=x;//入栈 
	vis[x]=1;//标记在栈内
	low[x]=num; 
    int zishu=0;//这是子树无疑了。
	for(int i=0;i<a[x].size();i++)
	{
         //对于无向边这里要注意反向边的问题,不能直接回到父亲
         if(a[x][i]==fa) continue;
		if(!dfn[a[x][i]])
		{
            zishu++;
			tayan(a[x][i],x);
			low[x]=min(low[x],low[a[x][i]]);
             if(dfn[x]<low[a[x][i]]) gebian++,gedian[x][a[x][i]]=1;//这里是割边无疑了
//这里是割点无疑
		}
		else if(vis[a[x][i]])//都不在栈里面了还更新个锤子 
		{
			low[x]=min(low[x],dfn[a[x][i]]);
		}
 
	}
   
	
void solve()
{
  for(int i=1;i<=n;i++)
  if(!dfn[i]) tayan(i,i);
} 

2-sat问题(可满足性问题)

给定一个布尔方程,求是否存在一组真值指派使整个方程为真。这样的方程是NP完全的,但是对于一定

条件限制下 的布尔方程,可以用强联通分量求解,也算是强联通分量的一个应用吧。

对于(a或b)我们可以这样连边(非a-b)^(非b-a),任意一个a或b都能化成这样的形式。

由于连通性,如果a和非a在同一个强联通分量(想一想为什么不是普通的联通分量)的话,方程无解。

如果a所在的强联通分量的拓补序在非a的强联通分量之后==a为真

为什么真值跟拓补序有关呢?

没有想得那么简单,因为假若一组是1 1 1 1的话,非1-1连边,1一定要保证是真的,1 0 1 0的话一定要保证1是假的,这样做是防止特殊条件的。

当然如果对于任意一组都是(a或b)a不等于b的话拓补序反过来也不是不行。

#include<bits/stdc++.h>
using namespace std;
int n,m,num=0,ans1=0,hao=0;
const int maxn=2000000+7;
int scc[maxn],dfn[maxn],zhan[maxn],ans[maxn],vis[maxn],low[maxn];
vector<int> tu[maxn]; 
void dfs(int x)
{
//	cerr<<"ceshi2"<<endl;
//	cout<<"x="<<x<<endl;
	dfn[x]=low[x]=++hao;
	zhan[++ans1]=x;
	vis[x]=1; 
	int t=tu[x].size();
	for(int i=0;i<t;i++)
	{
		if(!dfn[tu[x][i]]) dfs(tu[x][i]),low[x]=min(low[x],low[tu[x][i]]);
		else if(vis[tu[x][i]]) low[x]=min(low[x],dfn[tu[x][i]]);
	}
	if(dfn[x]==low[x])
	{
		num++;
		while(zhan[ans1]!=x)
		{
			scc[zhan[ans1]]=num;
			vis[zhan[ans1]]=0;
			ans1--;
		}
		scc[zhan[ans1]]=num;
		vis[zhan[ans1]]=0;
		ans1--;
	}
	return;
}
void qiang()
{
	for(int i=1;i<=n*2;i++)
	{
		if(!dfn[i]) dfs(i);
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int l,s,r,t;
		scanf("%d%d%d%d",&l,&s,&r,&t);
        tu[l+n*(s&1)].push_back(r+n*(t^1));
        tu[r+n*(t&1)].push_back(l+n*(s^1));
//        cout<<"bian"<<endl;
//        cout<<l+n*(s&1)<<" "<<r+n*(t^1)<<endl;
//        cout<<r+n*(t&1)<<" "<<l+n*(s^1)<<endl;
//		cout<<"bian"<<endl; 
	}
	
//	cerr<<"ceshi1"<<endl;
	qiang();
//	for(int i=1;i<=n;i++)
//	cout<<scc[i]<<" "<<scc[i+n]<<endl;
	for(int i=1;i<=n;i++)
	if(scc[i]==scc[i+n])
	{
		cout<<"IMPOSSIBLE"<<endl;
		return 0;
	}
	for(int i=1;i<=n;i++)
	if(scc[i]<scc[i+n]) ans[i]=1;
	else ans[i]=0;
	cout<<"POSSIBLE"<<endl;
	for(int i=1;i<=n;i++)
	{
		if(i!=n) printf("%d ",ans[i]);
		else printf("%d ",ans[i]);
	} 
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值