「刷题笔记」Tarjan

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

信息传递

讲个笑话:我之前用并查集求最小环过的这题,然后看见题目上有个\(tarjan\)标签
留下了深刻的印象:\(tarjan\)就是并查集求最小环
丢死人了
那么这题题意也很明确了,就是求一个最小环,并查集啥的就不想他了,考虑一下\(tarjan\)的做法
这道题里,就是我们求出每个强连通分量,然后看每个强连通分量最小大小是多少就好
贴一下板子qwq

#include
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 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],low[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<1)ans=min(ans,pp[i]);
	}
	printf("%lld",ans);
	return 0;
}

受欢迎的牛

由题意,我们首先可以得出,所有满足题意的牛都是在同一个强连通分量里的,但是给出的图中有许多强连通分量,哪些才是满足要求的呢?
首先,会不会有两个及以上满足要求的强连通分量呢?显然不行,因为那样就不符合强连通分量的定义了:

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

所以,我们只需要找出一个满足要求的强连通分量。
考虑要满足的要求,那么所有强连通分量都是有一条路通向我们所求的这个强连通分量的,再考虑前面强连通分量的定义,就会发现我们所求的这个强联通分量出度必须为\(0\)
与此同时,如果出现了多个出度为\(0\)的强连通分量,那么这两个强连通分量肯定是无法与彼此连通的,这种情况下,答案为\(0\)
同时,如果只有一个出度为\(0\)的强连通分量,那么答案即为这个强连通分量中包含点的个数。
码很好打,这里只是放一下,但这题思路着实挺有意思的awa

#include
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];
stackS;

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
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 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];
queueQ;
void SPFA(ll s)
{
	for(int i=1;i<=tot;i++)dis[i]=0;
	Q.push(s);
	dis[s]=pp[s];
	//cout<

备用交换机

割点板子
求割点的算法也叫\(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(v==fa)continue;
		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)