贴一个讲得非常详细的\(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. 较详细的gdb入门教程

    本文主要介绍gdb的基础使用.若需了解一些技巧,请访问此篇博客:点这里 本篇教程适用于Windows,macOS及Linux,但由于Windows的自带终端很难用,所以体验可能不太好.Windows ...

  2. 浅谈Linux桌面(发行版及桌面环境)

    Part I: 前言 笔者2018年接触Linux(当时还是学校机房的Ubuntu 14.04 LTS),至今已经有4个年头了. 折腾了至少十几个Linux发行版,包括但不限于: ubuntu.Deb ...

  3. 谈谈OKHttp的几道面试题

    来吧,今天说说常用的网络框架OKHttp,也是现在Android所用的原生网络框架(Android 4.4开始,HttpURLConnection的底层实现被Google改成了OkHttp),GOGO ...

  4. linux + MongoDB 安装 + 部署 + 讲解 (满满干货看完记得收藏噢)

    话不多说开始了! 安装 安装就依据菜鸟教程的进行安装 传送门 => https://www.runoob.com/mongodb/mongodb-linux-install.html 好啦!现在 ...

  5. inno steup 安装判断 进程是否运行

    1.添加了卸载判断用语 2.添加了安装程序进程是否存在使用了wmi服务 ; 脚本由 Inno Setup 脚本向导 生成! ; 有关创建 Inno Setup 脚本文件的详细资料请查阅帮助文档! #d ...

  6. NER的数据处理

    import os class TransferData: def __init__(self): cur = '/'.join(os.path.abspath(__file__).split('/' ...

  7. leetcode113:sudoku-solver

    题目描述 请编写一个程序,给数独中的剩余的空格填写上数字 空格用字符'.'表示 假设给定的数独只有唯一的解法 这盘数独的解法是: 红色表示填上的解 Write a program to solve a ...

  8. php 正则金额验证

    $money_reg = '/^[1-9]\d*|^[1-9]\d*.\d+[1-9]$/';if(!preg_match($money_reg, $money)){ $this->ajaxEr ...

  9. MFC的大致讲解

    现在使用MFC框架的人越来越少了,现在大家都在用QT框架来写,对应初学者就我感觉来说,MFC真的是一个很好的框架,现在在工业方面使用的几乎都是MFC,所以以后就业想要往工业方面找C++工作,可以好好看 ...

  10. http 怎样关闭

    如何优雅的关闭关闭这个fd , 如果只是一个简单的fd 直接调用close 就行, 但是如果要是一个框架 那就接到 资源回收复用 内存泄漏等问题: 来看看 ngx 是用怎样的思路处理 事务结束动作: ...