贴一个讲得非常详细的\(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. Kubernetes 1.13 的完整部署手册

    前言: 非常详细的K8s的完整部署手册,由于Kubernetes版本和操作系统的版本关系非常敏感,部署前请查阅版本关系对应表 地址:https://github.com/kubernetes/kube ...

  2. VC中句柄、指针、ID之间的转换

    win32直接操作的是句柄HANDLE,每个句柄就对应windows窗口,而vc对HANDLE进行类封装,间接操作的都是HANDLE,现在句柄只是类的一个成员变量. 从句柄到指针 CWnd* pWnd ...

  3. Windows 10 启动出现蓝屏 终止代码:UNMOUNTABLE_BOOT_VOLUME

    解决办法:在命令符窗口中[管理员权限] 1.– 修复Windows文件:损坏的Windows文件可能会导致严重的问题. sfc /scannow 2 .– 修复硬盘:确保您的硬盘依次运行,以及Wind ...

  4. Photoshop CC 习惯设置

    安装后,一般设置: 1.编辑--首选项--常规 2.一般更改内容为: 性能 内存使用情况在50%-80%之间 暂存盘:除去C盘意外的其他盘 单位和标尺:以px为单位 其他根据喜好设定!

  5. c语言博客作业——顺序结构,分支结构

    1.PTA截图 2.本章学习总结 2.1学习内容总结 数据的输入和输出:%d表示输入输出整数 %.lf表示输入浮点数 %.nf表示输出结果保留n位小数 if-else的分支结构可以有限个分类情况进行处 ...

  6. 炸了!一口气问了我18个JVM问题!

    前言 GC 对于Java 来说重要性不言而喻,不论是平日里对 JVM 的调优还是面试中的无情轰炸. 这篇文章我会以一问一答的方式来展开有关 GC 的内容. 不过在此之前强烈建议先看这篇文章深度揭秘垃圾 ...

  7. 【linux】helloword原理分析及实战

    目录 前言 linux中hello word原理 hello word 实战 学习参考 前言 hello word 著名演示程序,哈哈 下面在 arm linux 下展示一下hello world,便 ...

  8. 2020 中国.NET 开发者峰会正式启动

    2014年微软组织并成立.NET基金会,微软在成为主要的开源参与者的道路上又前进了一步. 2014年以来已经有众多知名公司加入.NET基金会,Google,微软,AWS三大云厂商已经齐聚.NET基金会 ...

  9. 1. 线性DP 300. 最长上升子序列 (LIS)

    最经典单串: 300. 最长上升子序列 (LIS) https://leetcode-cn.com/problems/longest-increasing-subsequence/submission ...

  10. linux之NTP服务

    1. NTP服务(网络时间协议) Network Time Protocol(NTP)是用来使计算机时间同步化的一种协议,它可以提供高精准度的时间校正(LAN上与标准间差小于1毫秒,WAN上几十毫秒) ...