贴一个讲得非常详细的\(tarjan\)入门教程

信息传递

讲个笑话:我之前用并查集求最小环过的这题,然后看见题目上有个\(tarjan\)标签

留下了深刻的印象:\(tarjan\)就是并查集求最小环

丢死人了

那么这题题意也很明确了,就是求一个最小环,并查集啥的就不想他了,考虑一下\(tarjan\)的做法

这道题里,就是我们求出每个强连通分量,然后看每个强连通分量最小大小是多少就好

贴一下板子qwq

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define ZZ_zuozhe int main()
#define E 200050 struct edge
{
ll u,v;
}e[E];
ll tot,head[E],next[E];
void add(ll a,ll b)
{
++tot;
e[tot].u=a;
e[tot].v=b;
next[tot]=head[a];
head[a]=tot;
} ll n,t; ll dfs[E],low[E],vis[E],cnt=0;
stack<ll> S;
ll pcn=0,pp[E];
void tarjan(ll u)
{
dfs[u]=low[u]=++cnt;
S.push(u);
vis[u]=1;
for(int i=head[u];i;i=next[i])
{
ll v=e[i].v;
if(!dfs[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])low[u]=min(low[u],dfs[v]);
}
if(dfs[u]==low[u])
{
++pcn;
ll tmp;
do{tmp=S.top();S.pop();pp[pcn]++;vis[tmp]=0;}while(tmp!=u);
}
} ZZ_zuozhe
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&t);
add(i,t);
}
for(int i=1;i<=n;i++)
{
if(!dfs[i])tarjan(i);
}
ll ans=E+1;
for(int i=1;i<=pcn;i++)
{
//cout<<pp[i]<<endl;
if(pp[i]>1)ans=min(ans,pp[i]);
}
printf("%lld",ans);
return 0;
}

受欢迎的牛

由题意,我们首先可以得出,所有满足题意的牛都是在同一个强连通分量里的,但是给出的图中有许多强连通分量,哪些才是满足要求的呢?

首先,会不会有两个及以上满足要求的强连通分量呢?显然不行,因为那样就不符合强连通分量的定义了:

有向图的极大强连通子图,称为强连通分量

所以,我们只需要找出一个满足要求的强连通分量。

考虑要满足的要求,那么所有强连通分量都是有一条路通向我们所求的这个强连通分量的,再考虑前面强连通分量的定义,就会发现我们所求的这个强联通分量出度必须为\(0\)

与此同时,如果出现了多个出度为\(0\)的强连通分量,那么这两个强连通分量肯定是无法与彼此连通的,这种情况下,答案为\(0\)

同时,如果只有一个出度为\(0\)的强连通分量,那么答案即为这个强连通分量中包含点的个数。

码很好打,这里只是放一下,但这题思路着实挺有意思的awa

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define ZZ_zuozhe int main()
#define N 10005
#define E 500005 struct edge
{
ll u,v;
}e[E];
ll tot=0,head[E],next[E];
void add(ll a,ll b)
{
++tot;
e[tot].u=a;
e[tot].v=b;
next[tot]=head[a];
head[a]=tot;
} ll n,m,a,b; ll dfs[E],low[E],vis[E],cnt=0;
ll pcn=0,pp[E],col[E],out[E];
stack<ll>S; void tarjan(ll u)
{
dfs[u]=low[u]=++cnt;
vis[u]=1;
S.push(u);
for(int i=head[u];i;i=next[i])
{
ll v=e[i].v;
if(!dfs[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])low[u]=min(low[u],dfs[v]);
}
if(low[u]==dfs[u])
{
++pcn;
ll tmp;
do{tmp=S.top();S.pop();vis[tmp]=0;pp[pcn]++;col[tmp]=pcn;}while(tmp!=u);
}
} ZZ_zuozhe
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%lld%lld",&a,&b);
add(a,b);
}
for(int i=1;i<=n;i++)
{
if(!dfs[i])tarjan(i);
}
for(int i=1;i<=n;i++)
{
for(int j=head[i];j;j=next[j])
{
if(col[i]!=col[e[j].v])out[col[i]]++;
}
}
bool flag=1;
ll ans=0;
for(int i=1;i<=pcn;i++)
{
if(out[i]==0)
{
if(flag)
{
ans=pp[i];
flag=0;
}
else
{
printf("0\n");
return 0;
}
}
}
printf("%lld\n",ans);
return 0;
}

拓展:

一个现在还并没有看懂的证明qaq

(感觉这个证明多加了个无环的条件,反正就先把所有\(DAG\)换成有向图理解吧qaq

[APIO2009]抢掠计划

tarjan缩点+\(spfa\)最长路

他可以经过同一路口或道路任意多次。

这个条件的存在告诉我们,因为有向图强连通分量中的点可以互相到达,那么如果走到了强连通分量其中的一个点,其实就相当于这个强连通分量中的点都走到了,既然如此,我们不妨将每个强连通分量看成一个点,并保留那些跨分量的边,再在得到的新图上用\(spfa\)跑最长路,最后枚举每个酒吧所处的强联通分量的\(dis\),取最大值即可得到答案。

缩点的过程是这样的:\(tarjan\)函数中,需要给各个结点染色,之后,先将原来存的边清空,再枚举原来的每条边(需要之前将两端点另存),如果发现他是跨分量的边,就把他两端点所在的强连通分量编号连边(注意保留原有方向),就建成了新图,应该是一个\(DAG\)。

然后就是正常的\(spfa\)了

考虑到降智严重的我有时会突然忘\(spfa\)板子,贴一下代码……

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define ZZ_zuozhe int main()
#define E 500050 struct edge
{
ll u,v,w;
}e[E];
ll tot=0,head[E],next[E];
void add(ll a,ll b,ll c)
{
tot++;
e[tot].u=a;
e[tot].v=b;
e[tot].w=c;
next[tot]=head[a];
head[a]=tot;
} ll n,m,a[E],b[E],s,p,t;
ll w[E];
ll bar[E];
stack<ll> S; ll vis[E],low[E],dfs[E],cnt=0;
ll pcn=0,pp[E],col[E];
void tarjan(ll u)
{
low[u]=dfs[u]=++cnt;
vis[u]=1;
S.push(u);
for(int i=head[u];i;i=next[i])
{
ll v=e[i].v;
if(!dfs[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])low[u]=min(low[u],dfs[v]);
}
if(low[u]==dfs[u])
{
ll tmp;
++pcn;
do{tmp=S.top();S.pop();vis[tmp]=0;col[tmp]=pcn;pp[pcn]+=w[tmp];}while(tmp!=u);
}
} ll dis[E];
queue<ll>Q;
void SPFA(ll s)
{
for(int i=1;i<=tot;i++)dis[i]=0;
Q.push(s);
dis[s]=pp[s];
//cout<<dis[s]<<endl;
vis[s]=1;
while(!Q.empty())
{
ll u=Q.front();Q.pop();
vis[u]=0;
for(int i=head[u];i;i=next[i])
{
ll v=e[i].v;
if(dis[v]<dis[u]+e[i].w)
{
dis[v]=dis[u]+e[i].w;
//cout<<dis[v]<<endl;
if(!vis[v])
{
Q.push(v);
vis[v]=1;
}
}
}
}
} ZZ_zuozhe
{
//freopen("owo.in","r",stdin);
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%lld%lld",&a[i],&b[i]);
add(a[i],b[i],0);
}
for(int i=1;i<=n;i++)scanf("%lld",&w[i]);
scanf("%lld%lld",&s,&p);
//cout<<p<<endl;
for(int i=1;i<=p;i++)
{
scanf("%lld",&t);
bar[i]=t;
//cout<<i<<' '<<<<endl;
}
for(int i=1;i<=n;i++)if(!dfs[i])tarjan(i);//cout<<i<<endl;
memset(e,0,sizeof e);
memset(head,0,sizeof head);
memset(next,0,sizeof next);
memset(vis,0,sizeof vis);
tot=0;
for(int i=1;i<=m;i++)
{
if(col[a[i]]!=col[b[i]])add(col[a[i]],col[b[i]],pp[col[b[i]]]);//cout<<a[i]<<' '<<b[i]<<' '<<col[a[i]]<<' '<<col[b[i]]<<' '<<pp[col[b[i]]]<<endl;
}
SPFA(col[s]);
ll ans=0;
for(int i=1;i<=p;i++)
{
//cout<<'\t'<<dis[col[bar[i]]]<<' '<<i<<' '<<bar[i]<<' '<<col[bar[i]]<<endl;
ans=max(ans,dis[col[bar[i]]]);
}
printf("%lld\n",ans);
return 0;
}

备用交换机

割点板子

求割点的算法也叫\(tarjan\)……不过似乎与求强连通分量那个有些微妙的差别,主要思想有相似之处

啊 这……我不知道有什么要讲了,贴一下这部分的板子吧qaq

void tarjan(ll u,ll fa)
{
dfn[u]=low[u]=++cnt;
ll ch=0;
for(int i=head[u];i;i=next[i])
{
ll v=e[i].v;
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=fa)cut[u]=1;
if(u==fa)ch++;
}
low[u]=min(low[u],dfn[v]);
}
if(ch>=2&&u==fa)cut[u]=1;
}

Trick or Treat on the Farm 采集糖果

我震惊了,这个数据范围,我上来居然不想记搜……T飞了……

还有一个点就是要注意自环,不然会爆栈

其他没有特别要说的了,本来是个板子题,然而因为\(sb\)错误弄了大半天qaq

关于low和dfn的问题……

上午刚会板子的时候教练问到了这个问题,其实就是求强连通分量的时候他如果搜到了到过的点,用来更新的不管是\(low\)还是\(dfn\)都肯定比当前\(low\)小,造成的结果也就都是这个点不会被当作根弹掉,所以实际上没啥影响

然后做割点的时候在题解区看到了这个,里面的解释挺详细的awa,总之就是求割点时换成\(low\)就会\(wa\),不过既然用\(dfn\)肯定不会错,又何必要改呢qaq,总之以后注意不要写错就是了

「刷题笔记」Tarjan的更多相关文章

  1. 「刷题笔记」AC自动机

    自动AC机 Keywords Research 板子题,同luoguP3808,不过是多测. 然后多测不清空,\(MLE\)两行泪. 板子放一下 #include<bits/stdc++.h&g ...

  2. 「刷题笔记」DP优化-状压-EX

    棋盘 需要注意的几点: 题面编号都是从0开始的,所以第1行实际指的是中间那行 对\(2^{32}\)取模,其实就是\(unsigned\ int\),直接自然溢出啥事没有 棋子攻击范围不会旋转 首先, ...

  3. 「刷题笔记」LCA问题相关

    板子 ll lg[40]; ll dep[N],fa[N][40]; ll dis[N]; void dfs(ll u,ll f) { dep[u]=dep[f]+1; fa[u][0]=f; for ...

  4. 「刷题笔记」哈希,kmp,trie

    Bovine Genomics 暴力 str hash+dp 设\(dp[i][j]\)为前\(i\)组匹配到第\(j\)位的方案数,则转移方程 \[dp[i][j+l]+=dp[i-1][j] \] ...

  5. 《Data Structures and Algorithm Analysis in C》学习与刷题笔记

    <Data Structures and Algorithm Analysis in C>学习与刷题笔记 为什么要学习DSAAC? 某个月黑风高的夜晚,下班的我走在黯淡无光.冷清无人的冲之 ...

  6. Python 刷题笔记

    Python 刷题笔记 本文记录了我在使用python刷题的时候遇到的知识点. 目录 Python 刷题笔记 选择.填空题 基本输入输出 sys.stdin 与input 运行脚本时传入参数 Pyth ...

  7. ☕【JVM技术指南】「JVM总结笔记」Java虚拟机垃圾回收认知和调优的"思南(司南)"【下部】

    承接上文 (完结撒花1-52系列)[JVM技术指南]「JVM总结笔记」Java虚拟机垃圾回收认知和调优的"思南(司南)"[上部] 并行收集器 并行收集器(也称为吞吐量收集器)是类似 ...

  8. PTA刷题笔记

    PTA刷题记录 仓库地址: https://github.com/Haorical/Code/tree/master/PTA/GPLT 两周之内刷完GPLT L2和L3的题,持续更新,包括AK代码,坑 ...

  9. 看完互联网大佬的「LeetCode 刷题手册」, 手撕了 400 道 Leetcode 算法题

    大家好,我是 程序员小熊 ,来自 大厂 的程序猿.相信绝大部分程序猿都有一个进大厂的梦想,但相较于以前,目前大厂的面试,只要是研发相关岗位,算法题基本少不了,所以现在很多人都会去刷 Leetcode ...

随机推荐

  1. TCP/IP 基础知识

    我把自己以往的文章汇总成为了 Github ,欢迎各位大佬 star https://github.com/crisxuan/bestJavaer 已提交此篇文章 要说我们接触计算机网络最多的协议,那 ...

  2. 删除指定路径下指定天数之前(以文件的创建日期为准)的文件:BAT + REG + Ritchie Lawrence 日期函数

    代码如下: @echo off::演示:删除指定路径下指定天数之前(以文件的创建日期为准)的文件.::如果演示结果无误,把del前面的echo去掉,即可实现真正删除.::本例调用了 Ritchie L ...

  3. C#4语法新特性

    C#4,.NET Framework 4.0, Visual Studio 2010  C#4.0新引进的语法基于.Net Framework 4.0.主要引进的语法:动态类型,命名参数.可选参数,优 ...

  4. Git撤销文件修改

    在旧版本中,git的撤销工作区的文件修改是用git checkout -- <file>命令,由于容易漏了--导致和切换分支混肴,所以新版本中: - 使用git restore (--wo ...

  5. leetcode110:combination-sum-ii

    题目描述 给出一组候选数C和一个目标数T,找出候选数中起来和等于T的所有组合. C中的每个数字在一个组合中只能使用一次. 注意: 题目中所有的数字(包括目标数T)都是正整数 组合中的数字 (a 1,  ...

  6. .Net5,C#9 新语法(逻辑和属性模式,记录)

    代码: namespace ConsoleApp1{ class Program { static void Main(string[] args) { //创建list数组,=号右边可省略 List ...

  7. Table is marked as crashed and should be repaired 解决办法

    遇到这个问题几个敲命令轻松搞定 1.首先进入mysql命令台: mysql -u root -p 回车  输入密码 2.查询所有的库 mysql> show databases; 3.进入数据库 ...

  8. Android studio设置参数文档提示

    方法/步骤     进行点击Android studio菜单中的file的选项菜单.   弹出了下拉菜单中进行选择为"settings"的选项的菜单即可.   进入到了settin ...

  9. 支持jewel版本的calamari

    之前测试了下,发现calamari不支持jewel版本的,是因为接口了有了一些变化,在提出这个问题后,作者给出了回答,说肯定会支持的,并且做了一点小的改动,就可以支持了,这个作者merge了到了git ...

  10. HBuilderX SVN地址更改(SVN服务器IP地址变更)

    HBuilderX编辑器中无法修改SVN地址,需要手动在SVN工具中修改 修改步骤: 1.右键编辑器中的SVN项目,选择打开文件所在目录 2.目录中空白处右键,选择TortoiseSVN --> ...