树的直径:
两次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]);
}
}