0.tarjan中变量定义
变量定义:
dfn[]:每个点的时间戳,代表到达该点的时间
low[]:不经过父节点能到达的时间戳
cnt:已经访问了几个节点
s[]:用于存放节点的栈
top:栈顶
scc[]:该节点属于第几个连通分量
sccnum:连通分量数量
iscut[]:是否是割点
1.tarjan求联通分量(有向图)
理解:
由于是求连通分量,根据连通分量的定义,我们可以得知连通分量是要求块内节点互相可达,因此只要形成环,环上的所有结点就能组成一个连通分量,用dfn数组保存每个点的访问时间,用low数组保存每个点不经过父节点能到达的最小时间,用数组s表示栈,存下这一路上的点,如果发现该点的dfn=low,那么则说明该点是这个联通分量的起始结点(因为只有形成了环才会dfn!=low),只要一直弹出结点,直到该结点dfn=low,就能将该连通分量上的点全部弹出来,然后一一标记属于哪个连通分量,注意可能存在多个块,所以要以每个没遍历过的点为起点遍历。
题目:
P3387 【模板】缩点
题目链接:
https://www.luogu.com.cn/problem/P3387
代码:
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long
#define IOS cin.sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
#pragma comment(linker, "/STACK:1024000000,1024000000")
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const double eps=1e-8;
const ll mod=1000000007;
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
/*const ll dx[8]={-1,-1,-1,0,0,1,1,1};
const ll dy[8]={-1,0,1,-1,1,-1,0,1};*/
const ll dx[4]={-1,1,0,0};
const ll dy[4]={0,0,-1,1};
ll qpow(ll a,ll b){ll s=1;while(b>0){if(b%2==1){s=s*a;}a=a*a;b=b>>1;}return s;}
ll qpowmod(ll a, ll b, ll c){ll res, t;res=1;t=a%c;while(b){if(b & 1){res=res*t%c;}t=t*t%c;b>>=1;}return res;}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;}
inline long long read(){long long k=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){k=k*10+ch-'0';ch=getchar();}return k*f;}
inline void write(long long x){if(x<0)x=-x,putchar('-');if(x>9)write(x/10);putchar(x%10+'0');}
// head
const int maxn=2e5+5;
ll n,m,val[10005],nval[10005],dp[10005],ans;
pii p[100005];
ll h[10005],tot;
ll dfn[10005],low[10005],cnt,s[10005],top,scc[10005],sccnum;
struct edge
{
int v,next;
}e[100005];
vector<int> v[10005];
bool vis[10005];
void add(int u,int v)
{
e[++tot]={v,h[u]};
h[u]=tot;
}
void tarjan(int x)
{
dfn[x]=low[x]=++cnt;
s[++top]=x;
for(int i=h[x];i;i=e[i].next)
{
int y=e[i].v;
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(!scc[y])
{
low[x]=min(low[x],dfn[y]);
}
}
if(dfn[x]==low[x])
{
sccnum++;
while(s[top]!=x)
{
scc[s[top]]=sccnum;
top--;
}
scc[s[top]]=sccnum;
top--;
}
}
void dfs(int x)
{
for(auto i:v[x])
{
dp[i]=max(dp[i],dp[x]+nval[i]);
ans=max(ans,dp[i]);
dfs(i);
}
}
signed main()
{
//IOS;
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>val[i];
}
for(int i=1;i<=m;i++)
{
cin>>p[i].first>>p[i].second;
add(p[i].first,p[i].second);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
{
tarjan(i);
}
}
for(int i=1;i<=n;i++)
{
nval[scc[i]]+=val[i];
ans=max(ans,nval[scc[i]]);
}
for(int i=1;i<=m;i++)
{
if(scc[p[i].first]!=scc[p[i].second])
{
v[scc[p[i].first]].push_back(scc[p[i].second]);
vis[scc[p[i].second]]=1;
}
}
for(int i=1;i<=sccnum;i++)
{
if(!vis[i])
{
dp[i]=nval[i];
dfs(i);
}
}
cout<<ans<<endl;
return 0;
}
/*
*/
2.tarjan求割点(无向图)
理解:
由于求的是割点,割点的定义是如果去掉一个点(包括连接该点的边)后,连通块数量变多了,就是割点。不妨想下怎么判割点,割点无非就是隔开了两边,如果该点的下一个点不能到已经遍历过的点(不包括该点),这意味着两个连通块不可以联通,被分开了。而我们定义的low数组正是这个意思,用于保存不通过父节点能到达的最小时间戳。因此只需判断low[v]>=dfn[u]就能知道该点是不是割点了。当然如果一个点有两个及以上个子树也是割点。
题目:
P3388 【模板】割点(割顶)
题目链接:
https://www.luogu.com.cn/problem/P3388
代码:
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long
#define IOS cin.sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
#pragma comment(linker, "/STACK:1024000000,1024000000")
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const double eps=1e-8;
const ll mod=1000000007;
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
/*const ll dx[8]={-1,-1,-1,0,0,1,1,1};
const ll dy[8]={-1,0,1,-1,1,-1,0,1};*/
const ll dx[4]={-1,1,0,0};
const ll dy[4]={0,0,-1,1};
ll qpow(ll a,ll b){ll s=1;while(b>0){if(b%2==1){s=s*a;}a=a*a;b=b>>1;}return s;}
ll qpowmod(ll a, ll b, ll c){ll res, t;res=1;t=a%c;while(b){if(b & 1){res=res*t%c;}t=t*t%c;b>>=1;}return res;}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;}
inline long long read(){long long k=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){k=k*10+ch-'0';ch=getchar();}return k*f;}
inline void write(long long x){if(x<0)x=-x,putchar('-');if(x>9)write(x/10);putchar(x%10+'0');}
// head
const int maxn=2e5+5;
ll n,m,ans;
ll h[20005],tot;
ll dfn[20005],low[20005],iscut[20005],cnt;
struct edge
{
int v,next;
}e[200005];
void add(int u,int v)
{
e[++tot]={v,h[u]};
h[u]=tot;
e[++tot]={u,h[v]};
h[v]=tot;
}
void tarjan(int x,int rt)
{
int child=0;
dfn[x]=low[x]=++cnt;
for(int i=h[x];i;i=e[i].next)
{
int y=e[i].v;
if(!dfn[y])
{
tarjan(y,rt);
low[x]=min(low[x],low[y]);
if(x==rt)
child++;
else if(low[y]>=dfn[x])
iscut[x]=1;
}
low[x]=min(low[x],dfn[y]);
}
if(x==rt&&child>=2)
iscut[x]=1;
}
signed main()
{
//IOS;
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
add(x,y);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
tarjan(i,i);
}
for(int i=1;i<=n;i++)
{
if(iscut[i])
ans++;
}
cout<<ans<<endl;
for(int i=1;i<=n;i++)
{
if(iscut[i])
cout<<i<<' ';
}
return 0;
}
/*
*/
3.tarjan求割边(无向图)
理解:
割边比割点要更严格,割点只要求不能通过父节点到达已到达的点(包括父节点)就是割点了,而割边要求的是不能通过父节点到达已到达的点(不包括父节点)就是割边,因为如果能不通过父节点到达父节点,说明光删这个边不足以分割成更多连通块。
题目:
P1656 炸铁路
题目链接:
https://www.luogu.com.cn/problem/P1656
代码:
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long
#define IOS cin.sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
#pragma comment(linker, "/STACK:1024000000,1024000000")
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const double eps=1e-8;
const ll mod=1000000007;
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
/*const ll dx[8]={-1,-1,-1,0,0,1,1,1};
const ll dy[8]={-1,0,1,-1,1,-1,0,1};*/
const ll dx[4]={-1,1,0,0};
const ll dy[4]={0,0,-1,1};
ll qpow(ll a,ll b){ll s=1;while(b>0){if(b%2==1){s=s*a;}a=a*a;b=b>>1;}return s;}
ll qpowmod(ll a, ll b, ll c){ll res, t;res=1;t=a%c;while(b){if(b & 1){res=res*t%c;}t=t*t%c;b>>=1;}return res;}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;}
inline long long read(){long long k=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){k=k*10+ch-'0';ch=getchar();}return k*f;}
inline void write(long long x){if(x<0)x=-x,putchar('-');if(x>9)write(x/10);putchar(x%10+'0');}
// head
const int maxn=2e5+5;
ll n,m;
ll h[20005],tot;
ll dfn[20005],low[20005],cnt;
set<pii> s;
struct edge
{
int v,next;
}e[200005];
void add(int u,int v)
{
e[++tot]={v,h[u]};
h[u]=tot;
}
void tarjan(int x,int pre)
{
dfn[x]=low[x]=++cnt;
for(int i=h[x];i;i=e[i].next)
{
int y=e[i].v;
if(!dfn[y])
{
tarjan(y,x);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x])
s.insert({x,y});
}
else if(y!=pre)
low[x]=min(low[x],dfn[y]);
}
}
signed main()
{
//IOS;
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
tarjan(i,i);
}
for(auto i:s)
cout<<i.first<<' '<<i.second<<endl;
return 0;
}
/*
*/