「刷题笔记」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的更多相关文章
- 「刷题笔记」AC自动机
自动AC机 Keywords Research 板子题,同luoguP3808,不过是多测. 然后多测不清空,\(MLE\)两行泪. 板子放一下 #include<bits/stdc++.h&g ...
- 「刷题笔记」DP优化-状压-EX
棋盘 需要注意的几点: 题面编号都是从0开始的,所以第1行实际指的是中间那行 对\(2^{32}\)取模,其实就是\(unsigned\ int\),直接自然溢出啥事没有 棋子攻击范围不会旋转 首先, ...
- 「刷题笔记」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 ...
- 「刷题笔记」哈希,kmp,trie
Bovine Genomics 暴力 str hash+dp 设\(dp[i][j]\)为前\(i\)组匹配到第\(j\)位的方案数,则转移方程 \[dp[i][j+l]+=dp[i-1][j] \] ...
- 《Data Structures and Algorithm Analysis in C》学习与刷题笔记
<Data Structures and Algorithm Analysis in C>学习与刷题笔记 为什么要学习DSAAC? 某个月黑风高的夜晚,下班的我走在黯淡无光.冷清无人的冲之 ...
- Python 刷题笔记
Python 刷题笔记 本文记录了我在使用python刷题的时候遇到的知识点. 目录 Python 刷题笔记 选择.填空题 基本输入输出 sys.stdin 与input 运行脚本时传入参数 Pyth ...
- ☕【JVM技术指南】「JVM总结笔记」Java虚拟机垃圾回收认知和调优的"思南(司南)"【下部】
承接上文 (完结撒花1-52系列)[JVM技术指南]「JVM总结笔记」Java虚拟机垃圾回收认知和调优的"思南(司南)"[上部] 并行收集器 并行收集器(也称为吞吐量收集器)是类似 ...
- PTA刷题笔记
PTA刷题记录 仓库地址: https://github.com/Haorical/Code/tree/master/PTA/GPLT 两周之内刷完GPLT L2和L3的题,持续更新,包括AK代码,坑 ...
- 看完互联网大佬的「LeetCode 刷题手册」, 手撕了 400 道 Leetcode 算法题
大家好,我是 程序员小熊 ,来自 大厂 的程序猿.相信绝大部分程序猿都有一个进大厂的梦想,但相较于以前,目前大厂的面试,只要是研发相关岗位,算法题基本少不了,所以现在很多人都会去刷 Leetcode ...
随机推荐
- [Luogu P2824] [HEOI2016/TJOI2016]排序 (线段树+二分答案)
题面 传送门:https://www.luogu.org/problemnew/show/P2824 Solution 这题极其巧妙. 首先,如果直接做m次排序,显然会T得起飞. 注意一点:我们只需要 ...
- update和insert
sprintf(sql, "INSERT lora_net(eui,online) VALUES('%s', 1) ON DUPLICATE KEY UPDATE online=1" ...
- SpringBoot魔法堂:说说带智能提示的spring-boot-starter
前言 前几个月和隔壁组的老王闲聊,他说项目的供应商离职率居高不下,最近还有开发刚接手ESB订阅发布接口才两周就提出离职,而他能做的就只有苦笑和默默地接过这个烂摊子了. 而然幸福的家庭总是相似的,而不幸 ...
- flex-shrink值的计算
flex-shrink为弹性盒模型中,当弹性项不断行,并且所有弹性项的宽度只和大于弹性盒模型的可分配空间时,弹性项的收缩程度. 找到英文资料对flex-shrink的定义描述: flex-shrink ...
- C++ Split string into vector<string> by space(转)
c++中没有这么方便的实现,但也有很多的方法能实现这个功能,下面列出五种常用的实现的方法,请根据需要选择,个人觉得前三种使用起来比较方便,参见代码如下: #include <vector> ...
- tigervnc 编译
1.根据BUILDING.txt安装依赖(建议使用默认路径) 其中X11 development kit使用 yum install libX11* 2.修改CMakeList.txt 在前面添加 s ...
- Linux踩坑之云服务器 ssh 连接不上
前奏:今天没事处理一下之前远程不了Linux桌面的问题时,找到一个解决方法(开始入坑): systemctl set-default graphical.tar ...
- Innodb表空间迁移过程
1.大致流程 将a实例的表的数据迁移到b实例上. 1.在目标实例b上创建一个相同的表 2.在目标库b上执行ALTER TABLE t DISCARD TABLESPACE; 3.在源库a上执行FLUS ...
- 希捷powerchoice磁盘休眠功能arm打包
官方只提供了x86下面的包,没有提供arm下面的包,而我们的arm机器是32位的,需要编译一个支持armhf的二进制文件,这个文件只需要一个即可,但是编译是整套编译的,并且我们需要选定指定的版本,关闭 ...
- a^b(取模运算)
a^b(sdtbu oj 1222) Description 对于任意两个正整数a,b(0 <= a, b < 10000)计算ab各位数字的和的各位数字的和的各位数字的和的各位数字的和. ...