[tarjan缩点][拓扑排序]Trick Or Treat On The Farm

本文介绍了一种算法解决方案,用于解决一个有趣的问题:如何计算每只奶牛在遵循特定路径规则的情况下能够收集到的糖果数量。通过使用Tarjan算法进行强连通分量分析,并结合拓扑排序,实现了对问题的有效求解。

题目描述

每年万圣节,威斯康星的奶牛们都要打扮一番,出门在农场的N个牛棚里转 悠,来采集糖果.她们每走到一个未曾经过的牛棚,就会采集这个棚里的1颗糖果.

农场不大,所以约翰要想尽法子让奶牛们得到快乐.他给每一个牛棚设置了一个“后继牛 棚”.牛棚i的后继牛棚是next_i 他告诉奶牛们,她们到了一个牛棚之后,只要再往后继牛棚走去, 就可以搜集到很多糖果.事实上这是一种有点欺骗意味的手段,来节约他的糖果.

第i只奶牛从牛棚i开始她的旅程.请你计算,每一只奶牛可以采集到多少糖果.

输入输出格式

输入格式:
第一行一个正整数n(1≤n≤10^5)
接下来n行,第i行一个正整数表示第i-1号牛棚通向的牛棚

输出格式:
总共n行,第i行表示第i号牛棚开始得到的糖果数

输入输出样例

输入样例
4
1
3
2
3
输出样例
1
2
2
3

分析

首先我们知道只有n条边
然后我们发现,n条边足够组成强联通分量
所以我们用tarjan缩点
缩点以后将拓扑序列反过来继承糖果数输出即可

#include <iostream>
#include <cstdio>
#include <queue>
#define rep(i,a,b) for (i=a;i<=b;i++)
using namespace std;
int n;
int list[100001],u[100001],v[100001];
int f[100001];

int low[100001],dfn[100001],target;
int stk[100001],top;
bool instk[100001];
int d[100001],cnt;

queue<int> q;
int w;
int p[100001],ind[100001];
void tarjan(int i)
{
    low[i]=dfn[i]=++target;
    stk[++top]=i;instk[i]=1;
    if (!dfn[v[i]])
    {
        tarjan(v[i]);
        low[i]=min(low[i],low[v[i]]);
    }
    else
    if (instk[v[i]])
    low[i]=min(low[i],dfn[v[i]]);
    if (low[i]==dfn[i])
    {
        int s=0,o;
        cnt++;
        do
        {
            o=stk[top];
            top--;
            d[o]=cnt;
            instk[o]=0;
            s++;
        }
        while (i!=o);
        f[cnt]=s;
    }
}
void topsort()
{
    int i;
    rep(i,1,cnt)
    if (!ind[i]) q.push(i);
    while (!q.empty())
    {
        int k=q.front();
        q.pop();
        p[++w]=k;
        if (!(--ind[v[list[k]]])) q.push(v[list[k]]);
    }
}
void init()
{
    int i;
    scanf("%d",&n);
    rep(i,1,n)
    {
        scanf("%d",&v[i]);
        u[i]=i;
    }
}
void doit()
{
    int i;
    rep(i,1,n)
    if (!dfn[i]) tarjan(i);
    rep(i,1,n)
    if (d[i]!=d[v[i]])
    {
        u[i]=d[i];v[i]=d[v[i]];
        list[u[i]]=i;
        ind[v[i]]++;
    }
    topsort();
    for (i=w;i>=1;i--)
    f[p[i]]+=f[v[list[p[i]]]];
}
void print()
{
    int i;
    rep(i,1,n)
    printf("%d\n",f[d[i]]);
}
int main()
{
    init();
    doit();
    print();
}
### 原理 Tarjan是针对有向图的操作。强连通分量指里面的两两之间互相可达,当一道题中互相可达的有某种等价联系(一个强连通分量等价于一个的作用)时,就可以进行Tarjan算法通过DFS序和low数组,利用回溯信息判断图的连通性结构。对于有向图求强连通分量需借助栈。后图会变成一个有向无环图(DAG),因为强连通分量被合并成了一个,消除了环的存在 [^2][^3]。 ### 实现 虽然引用未给出具体代码实现,但Tarjan实现的大致步骤如下: 1. 初始化`dfn`数组记录DFS序,`low`数组记录能追溯到的最早的节的DFS序,栈用于辅助寻找强连通分量。 2. 从一个未访问过的节开始进行深度优先搜索(DFS)。 3. 在DFS过程中,更新`dfn`和`low`数组。 4. 当一个节的`dfn`和`low`相等时,说明找到了一个强连通分量,将栈中元素弹出直到该节,这些元素构成一个强连通分量。 5. 把每个强连通分量成一个,重新构建图。 ### 应用 - **提高效率**:可以把一些等价的的操作一起做,避免对等价进行重复操作,加快计算速度 [^2]。 - **处理DAG上的算法**:之后的图必然是一个DAG,可以在上面进行拓扑排序、动态规划(dp)等操作 [^2]。 - **解决特定类型题目**:如在一些图论题目中,是重要的解题步骤,像模板题P3387 【模板】 [^4]。 ```python # 以下是一个简单的伪代码示例来展示Tarjan的大致框架 def tarjan(u): global index dfn[u] = low[u] = index index += 1 stack.append(u) in_stack[u] = True for v in graph[u]: if dfn[v] == 0: tarjan(v) low[u] = min(low[u], low[v]) elif in_stack[v]: low[u] = min(low[u], dfn[v]) if dfn[u] == low[u]: scc = [] while True: v = stack.pop() in_stack[v] = False scc.append(v) if v == u: break # 这里可以进行操作,例如标记每个所属的强连通分量 for node in scc: scc_id[node] = len(scc_list) scc_list.append(scc) # 初始化 n = 10 # 节数量 graph = [[] for _ in range(n)] dfn = [0] * n low = [0] * n stack = [] in_stack = [False] * n index = 1 scc_id = [-1] * n scc_list = [] # 假设已经构建好图graph # 对每个未访问的节进行Tarjan算法 for i in range(n): if dfn[i] == 0: tarjan(i) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值