tarjan小白入门

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;
}
/*

*/
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值