[USACO08DEC]Trick or Treat on the Farm

题意

给定一个奶牛数N(1\leq N \leq 10^5),初始时奶牛iith位置上,每个位置可以移动到next_i位置上,求每个奶牛移动的最多次数(对于一个奶牛,一个点至多访问一次).

分析

由于本题一个点只有一条出边,所以图构成的环应该是一个简单环,且一个环上可以收集的价值相同。

应用缩点后dp的思想,将图分为链与简单环进行处理
- 维护两个染色变量pre\_vis,now\_vis,分别处理环与加入环的链
- 环通过dfs维护路径长度dis[u]=times,优先处理环
- 当pre\_vis[u]==true时,该环收集的价值num=times-dis[u](可以理解为dfs的访问后向边两次dts时间戳的差值)
- now\_vis[u]确保每个环只处理一次
- 若点不在环上(没被优先处理(cnt[u]==0)),则cnt[u]=cnt[e[u]]+1

PS:还有更猛的做法: 1°记录每个点入度.  2°做一次dfs,确定深度优先森林,同时每个点的入度都减1.  3°还有入度的点即对应了一条后向边(u,v)v点,从而预处理环(这里运用了环为简单环的性质).  4°对链dp即可

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int MAXN = 1e5 + 5;
int N;
int e[MAXN], cnt[MAXN], dis[MAXN];
bool pre_vis[MAXN], now_vis[MAXN];
void dfs(int, int);

int main(){
  freopen("indata.txt", "r", stdin);
  freopen("outdata.txt", "w", stdout);
  ios::sync_with_stdio(false);
  int i;
  cin >> N;
  for(i = 1; i <= N; i++) cin >> e[i];
  for(i = 1; i <= N; i++)
    if(!pre_vis[i]) dfs(i, 0);
  for(i = 1; i <= N; i++) cout << cnt[i] << endl;
  return 0;
}

bool circle_vis[MAXN];
void manage_circle(int, int);
void dfs(int u, int times){
  if(now_vis[u]) return;
  if(pre_vis[u]){
    manage_circle(u, times - dis[u]);
    return;
  }
  pre_vis[u] = true, dis[u] = times;
  dfs(e[u], times + 1);
  if(!cnt[u]) cnt[u] = cnt[e[u]] + 1;
  now_vis[u] = true;
}

void manage_circle(int u, int num){
  if(circle_vis[u]) return;
  circle_vis[u] = true, cnt[u] = num;
  manage_circle(e[u], num);
}

猜你喜欢

转载自blog.csdn.net/Hardict/article/details/82712024