luogu P2921 [USACO08DEC]在农场万圣节Trick or Treat on the Farm

本文介绍了一道USACO编程题的解题思路,利用非递归方法解决奶牛在限定路径中收集糖果的问题。通过设置节点颜色和时间戳,巧妙地计算出每只奶牛能访问的隔间数量。

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

转自:题解 P2921 【[USACO08DEC]在农场万圣节Trick or Treat on the Farm】

题目链接: P2921 [USACO08DEC]在农场万圣节Trick or Treat on the Farm

题目描述

每年,在威斯康星州,奶牛们都会穿上衣服,收集农夫约翰在N(1<=N<=100,000)个牛棚隔间中留下的糖果,以此来庆祝美国秋天的万圣节。

由于牛棚不太大,FJ通过指定奶牛必须遵循的穿越路线来确保奶牛的乐趣。为了实现这个让奶牛在牛棚里来回穿梭的方案,FJ在第i号隔间上张贴了一个“下一个隔间”Next_i(1<=Next_i<=N),告诉奶牛要去的下一个隔间;这样,为了收集它们的糖果,奶牛就会在牛棚里来回穿梭了。

FJ命令奶牛i应该从i号隔间开始收集糖果。如果一只奶牛回到某一个她已经去过的隔间,她就会停止收集糖果。

在被迫停止收集糖果之前,计算一下每头奶牛要前往的隔间数(包含起点)。

输入格式

第1行 整数n。

第2行到n+1行 每行包含一个整数 next_i 。

输出格式

n行,第i行包含一个整数,表示第i只奶牛要前往的隔间数。

输入输出样例

输入样例#1: 

4 
1 
3 
2 
3 

输出样例#1: 复制

1 
2 
2 
3 

 

样例解释

有4个隔间

隔间1要求牛到隔间1

隔间2要求牛到隔间3

隔间3要求牛到隔间2

隔间4要求牛到隔间3

牛1,从1号隔间出发,总共访问1个隔间;

牛2,从2号隔间出发,然后到三号隔间,然后到2号隔间,终止,总共访问2个隔间;

牛3,从3号隔间出发,然后到2号隔间,然后到3号隔间,终止,总共访问2个隔间;

牛4,从4号隔间出发,然后到3号隔间,然后到2号隔间,然后到3号隔间,终止,总共访问3个隔间。

翻译提供者:吃葡萄吐糖

——————————————————————————分割线———————————————————————————

做完以后粗略翻了下题解,发现都是 TarjanTarjan 或记忆化搜索,总之逃不出 dfsdfs,所以我就把我的非递归方法贡献一下吧。

事实上,这道题用 TarjanTarjan 是大材小用了。此题不需要任何算法,两层简单的循环就能解决。

首先我们需要注意到一点,虽然此题也是一张有向图,但是每个点的出度有且只有 “1”。这说明什么?不需要递归,直接沿着这条唯一的路径走下去就行了......

一、为了实现这一方法,我们对每个点设置两个属性:

1、颜色 (color)(color) : 此节点第一次被访问时,这条访问他的路径是由那个节点发出的(起点)。

2、时间戳 (dfn)(dfn) :此节点第一次被访问时,他到发出这条路径的起点的距离(发出节点的 dfn = 0dfn=0,第二个被访问的节点的 dfn = 1dfn=1,第三个 dfn = 2dfn=2 ......)

有了这两个属性,我们就可以计算环的大小,方法如下:

1、从某一节点发出路径

2、走到某个节点上(包括起点),如果这个节点没有被染色,那么染成自己的颜色,并标记上 dfndfn

3、走到某个节点上,如果这个节点已经被染成了自己的颜色,那么环的大小就出来了:当前时间 (cnt)(cnt) -− 此节点 dfndfn

到了这一步,实际上已经解决了另一个更简单的问题:NOIP2015 信息传递。 接下来就是本题特色了

二、对于每一只奶牛(或者说每一个起点、颜色、路径),我们记录如下两个属性:

1、环的大小 (minc)(minc) :每条路径最终都会进入环中,或者起点本身就在环中,我们记录下这个环的大小为之后服务

2、入环时间戳 (sucdfn)(sucdfn) :这条路径什么时候会进入环中,同样是为之后服务的一个属性

首先讲解一下如果得到这两个属性:

1、在上一节中我们已经讲了如何初步获取环的大小,入环时间戳只要记录为那个交点的时间戳即可

2、如果走到了之前走过的节点,那么新的路径必然进入之前路径的环中,直接把这个环的大小要过来就行了。入环时间戳则分两种情况:

i. 如果这个节点不在环中,“原路径的入环时间戳 -− 原路径中此节点的时间戳 + 新路径当前时间” 即为新路径的入环时间戳;

ii. 而如果这个节点在环中,直接就是新路径当前时间。

iii. 判断方法则是 “原路径的入环时间戳 -− 原路径中此节点的时间戳” 是否大于 00,综合起来就是:“max(max(原路径的入环时间戳 -− 原路径中此节点的时间戳, \;0),0) + 新路径当前时间”

三、把上面的问题都解决了,出答案就太简单了:

1、第一节中的发现环的大小之后,答案就是“当前时间”

2、第二节中与之间走过的节点相遇并记录完信息后,答案是 “入环时间戳 + 环的大小”

至此本题已经完美解决,且没有用到任何算法。贴代码:

#include<bits/stdc++.h>

using namespace std;

const int maxn = 100000 + 5;

int n;
int nxt[maxn];
int color[maxn];
int sucdfn[maxn];
int dfn[maxn];
int minc[maxn];

void Init()
{
    cin >> n;
    for(int i = 1; i <= n; ++i) cin >> nxt[i];
    memset(color, 0, sizeof(color));
    memset(dfn, 0, sizeof(dfn));
    memset(minc, 0, sizeof(minc));
}

void Solve()
{
    for(int cow = 1; cow <= n; ++cow) 
    {
        for(int i = cow, cnt = 0; ; i = nxt[i], ++cnt)
        {
            if(!color[i]) {
                color[i] = cow;
                dfn[i] = cnt;
            }
            else if(color[i] == cow) {
                minc[cow] = cnt - dfn[i];
                sucdfn[cow] = dfn[i];
                cout << cnt << endl;
                break;
            }
            else {
                minc[cow] = minc[color[i]];
                sucdfn[cow] = cnt + max(sucdfn[color[i]] - dfn[i], 0);
                cout << sucdfn[cow] + minc[cow] << endl;
                break;
            }
        }
    }
} 

int main()
{
    Init();
    Solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值