Live2D

Solution -「LOCAL」人口迁徙

Description

  n 个点,第 i 个点能走向第 di 个点,但从一个点出发至多走 k 步。对于每个点,求有多少点能够走到它。

  n5×105

Solution

  显然这些点构成一片内向基环树森林。考虑每个点的贡献,若其向上走 k 步仍不能到环上,那一定只在树内对一条链贡献,树上差分一下。否则,该点会向到根的每个点贡献,并向环上一段连续的点贡献,后者维护一个数列差分亦可统计,就切掉啦。

  复杂度 O(n)

Code

#include <cstdio>
#include <vector>
#include <algorithm>

inline int rint () {
	int x = 0; char s = getchar ();
	for ( ; s < '0' || '9' < s; s = getchar () );
	for ( ; '0' <= s && s <= '9'; s = getchar () ) x = x * 10 + ( s ^ '0' );
	return x;
}

inline void wint ( const int x ) {
	if ( 9 < x ) wint ( x / 10 );
	putchar ( x % 10 ^ '0' );
}

const int MAXN = 5e5;
int n, K, d[MAXN + 5], ans[MAXN + 5], stk[MAXN + 5], top, ccnt;
bool vis[MAXN + 5], oncir[MAXN + 5], inq[MAXN + 5];
int dep[MAXN + 5], fa[MAXN + 5], dir[MAXN + 5], tag[MAXN + 5], add[MAXN * 2 + 5];
std::vector<int> cir[MAXN + 5], adj[MAXN + 5];

inline void findCir ( const int u ) {
	if ( vis[u] ) return ;
	vis[u] = true, stk[++ top] = u, inq[u] = true;
	if ( inq[d[u]] ) {
		int v; ++ ccnt;
		do {
			oncir[v = stk[top --]] = true;
			cir[ccnt].push_back ( v );
		} while ( v ^ d[u] );
		std::reverse ( cir[ccnt].begin (), cir[ccnt].end () );
	}
	findCir ( d[u] ), inq[u] = false;
}

inline void solveTree ( const int u, int p, const int rtid, const int L ) {
	++ tag[u];
	if ( dep[u] <= K ) {
		int tar = K - dep[u] + 1;
		if ( tar >= L ) tar = L - 1;
		++ add[rtid + 1], -- add[rtid + 1 + tar];
	} else {
		-- tag[fa[p]], p = dir[p];
	}
	for ( int i = 0, v; i ^ adj[u].size (); ++ i ) {
		if ( !oncir[v = adj[u][i]] ) {
			dep[dir[u] = v] = dep[u] + 1, fa[v] = u;
			solveTree ( v, p, rtid, L );
		}
	}
}

inline int calc ( const int u ) {
	int ret = tag[u];
	for ( int i = 0, v; i ^ adj[u].size (); ++ i ) {
		if ( !oncir[v = adj[u][i]] ) {
			ret += calc ( v );
		}
	}
	return ans[u] += ret, ret;
}

inline void solveCir ( std::vector<int>& cir ) {
	int L = cir.size ();
	for ( int i = 0; i <= L << 1; ++ i ) add[i] = 0;
	for ( int i = 0, u; i < L; ++ i ) {
		dep[u = cir[i]] = 1;
		solveTree ( u, u, i, L ), calc ( u );
	}
	for ( int i = 1; i < L * 2; ++ i ) add[i] += add[i - 1];
	for ( int i = 0; i < L; ++ i ) ans[cir[i]] += add[i] + add[i + L];
}

int main () {
	freopen ( "travel.in", "r", stdin );
	freopen ( "travel.out", "w", stdout );
	n = rint (), K = rint ();
	for ( int i = 1; i <= n; ++ i ) adj[d[i] = rint ()].push_back ( i );
	for ( int i = 1; i <= n; ++ i ) top = 0, findCir ( i );
	for ( int i = 1; i <= ccnt; ++ i ) solveCir ( cir[i] );
	for ( int i = 1; i <= n; ++ i ) wint ( ans[i] ), putchar ( '\n' );
	return 0;
}

Details

  考场上想得久了点 qwq……最神奇的是兔子爆排一般是因为标算写假而测试代码是对的。(

posted @   Rainybunny  阅读(137)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示