【[USACO08DEC]在农场万圣节Trick or Treat on the Farm】题解学习 && 第一篇博客祭

本文介绍了一种解决有向图循环遍历问题的高效算法,通过两次遍历实现,首次遍历标记各点所属路径及访问顺序,二次遍历则计算环的大小和入环时间戳,适用于每个点仅有一条出边的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里是蒟蒻我的第一篇博客qwq。用来记录大蒟子学习OI的心路历程以及学习经验的个人总结。如果有错误,请各位看官老爷及时指出,咱一定会改,毕竟要一直学习嘛。各种思路及写法仅供参考,你拿去A题也无所谓嘛。。

正文

题目大意

一张有向图,共n个点,每一个点i都有且只有一条出边Next[i](可以指向自己,即有自环)。现有n头牛分别从n个点出发,走到每个点所指向的那个点,一直走下去,直到再次走到已走过的点为止,求每头牛走过的房间数(最后一个重复的点不算)。

分析

显然题目是与环有关,可以用强连通。。。但是有dalao说太大材小用了,因为每个点只有一个出边,所以根本不需要递归,用两个for就够了。

安利大佬博文,尊重版权,先%为敬。
https://www.luogu.org/blog/planetarian/solution-p2921

所以这里我们只需要

一、先处理每一个点。

来波定义。

const int AC=100005;
int to[AC],color[AC],dfn[AC];

to显然是存的每一个点所能到达的另一个点。
从起始点cow开始遍历,枚举。
cnt记录当前步数(访问点的顺序)。
记录点i的起始点color[i],以及从起始点cow访问到它的次序dfn[i]。(起始点的dfn=0,访问到的第一个点dfn=1……)

for ( int cow=1;cow<=n;cow++ ){
    for ( int i=cow,cnt=0;;i=to[i],cnt++){
    ……
    ……
    }
}

搜到一个点时,若没有被访问过,则将点i的起始点color[i]设为cow,并把其在以cow为起始点的路径中的访问顺序dfn[i]赋值为当前的步数。

if ( !color[i] ){
    color[i]=cow;
    dfn[i]=cnt;
}

二、处理路径(环)

再来一波定义

int mir[AC],suc[AC];

mir[i]是点i所在环的大小,suc[i]是点i进入该环的时间戳。

这里有人就会问,时间戳是什么???

时间戳在这里,是指以某一点为起点时,第一次进入该环的步数。其实刚才的dfn也是时间戳……

求环的大小mir

对于在以某个起始点开始遍历的路径来说,如果当前访问的点为i,color[i]已经被标记为当前所遍历路径的起始点,那么显然,环的大小就是当前的步数(cnt)-当前遍历点的初次遍历时间戳(dfn[i])。而此时的入环时间戳显然就是第一次遍历该点时的时间戳(dfn[i])。

注意:环的大小和入环时间戳都是以起始点为依据存储的!

if ( color[i]==cow ){
    mir[cow]=cnt-dfn[i];
    suc[cow]=dfn[i];
}
调用已有答案

划重点!!!

总感觉那位大佬这块讲的不是很明白(认真划掉)

我们先假设当前已求出了对某一个点向后遍历的路径的各种东西。

现在我们要遍历一个新的路径,我们设这个序列里的某点为x。

如果某一个x遍历到了已经求过的路径中的某个点y,那么这里y存在两种情况。

点y在环中

显然这样新路径上的入环时间戳就是当前的步数。

    suc[新路径]=cnt;

点y不在环中

如果是这样,那y点距离其路径上的环一定有一段距离,那么这段距离显然是原路径的入环时间戳-当前遍历到的点的时间戳(请认真多想几遍)。
那么,新路径的入环时间戳就是这段距离+新路径当前的步数

    suc[新路径]=(suc[原路径]-dfn[y])+cnt;

整理一下,显然能得到如下公式。

    suc[新路径]=cnt+max(suc[原路径]-dfn[y],0);

三、处理答案

在第一部分处理每一个点的过程中,找到环的大小之后,答案就是“当前时间”。(因为找到环就是走到了已访问过的点)
第二部分处理路径中,与之前访问过的节点相遇并更新当前新路径信息后,答案是 “入环时间戳 + 环的大小”

完结撒花!!!

下面激动人心的上代码:

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<queue>
#include<math.h>
#include<vector>

using namespace std;

const int AC=100005;

int n,to[AC];
int color[AC],dfn[AC];
int mir[AC],suc[AC];

int main(){
    scanf("%d",&n);
    memset(color,0,sizeof(color));
    memset(dfn,0,sizeof(dfn));
    memset(mir,0,sizeof(mir));
    for ( int i=1;i<=n;i++ ){
        scanf("%d",&to[i]);
    }
    for ( int cow=1;cow<=n;cow++ ){
        for ( int i=cow,cnt=0;;i=to[i],cnt++){
            if ( !color[i] ){
                color[i]=cow;
                dfn[i]=cnt;
            }
            else if ( color[i]==cow ){
                mir[cow]=cnt-dfn[i];
                suc[cow]=dfn[i];
                printf("%d\n",cnt);
                break;
            }
            else{
                mir[cow]=mir[color[i]];
                suc[cow]=cnt+max(suc[color[i]]-dfn[i],0);
                printf("%d\n",suc[cow]+mir[cow]);
                break;
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值