K. 复合函数 (基环树)

2022 CCPC Henan Provincial Collegiate Programming Contest

Problem

在这里插入图片描述

Solution

在这里插入图片描述
在这里插入图片描述

实现思路

  1. 首先可以发现, 从所有的点出发,最终都会进入一个环中。所以可以构建一个 “基环森林”。
  2. 把当前的这个联通块找出来,并单独存起来
  3. 然后bfs拓扑排序把不在环上的点标记,剩下的就是环上的结点
  4. 枚举环上的结点,搜索每一个以环上结点为根节点的子树
  5. 统计大小相同的环上,不同深度的结点个数
  6. 求一下前缀和,求出深度不超过d的结点有多少个
  7. 然后枚举可以整除 ∣ a − b ∣ |a - b| ab 的环长,把深度小于 m i n ( a , b ) min(a, b) min(a,b) 的结点累加到答案里面即可
  8. 最后注意:环长最多有 n \sqrt n n 种,有点不太准确,容易 Runtime Error
    准确的是:环长有 2 n 2 \sqrt n 2n
    1 + 2 + 3 + . . . + k = k ( k + 1 ) / 2 ≤ n 1 + 2 + 3 + ... + k = k(k + 1) / 2 \leq n 1+2+3+...+k=k(k+1)/2n k ≤ 2 n k \leq 2 \sqrt n k2n

Code

const int N = 1e5 + 5;
int a[N];
int vis[N], deg[N];
vector<int> v[N], node;
int hoop[500][N];
 
unordered_map<int, int> mp;
int c[N], t;//第i中环长的长度,环长的种类数
int solve(int len)//离散化环的长度
{
	if (mp.count(len) == 0) 
		c[++t] = len, mp[len] = t;
	return mp[len];
}
 
void dfs(int x)
{
	vis[x] = 1;
	for (int y : v[x])
	{
		if (vis[y]) continue;
		node.push_back(y);
		dfs(y);
	}
}
 
void dfs2(int x, int deep, int len) //以环上结点为根,搜索其子树
{
	for (int y : v[x])
	{
		if (vis[y]) continue;
		vis[y] = 2;//防止非环上结点与环上结点矛盾,使用 2 代表遍历过的非环结点
		hoop[solve(len)][deep + 1]++;
		dfs2(y, deep + 1, len);
	}
}
 
void bfs()
{
	queue<int> q;
	for (int x : node)
		if (deg[x] == 1) q.push(x);
 
	int len = sz(node);//环长
	while (sz(q))
	{
		int x = q.front(); q.pop();
		vis[x] = 0;//0为非环结点, 1 为环上结点
		len--;
		for (int y : v[x])
			if (--deg[y] == 1) q.push(y);
	}
 
	for (int x : node)
		if (vis[x] == 1)
		{
			hoop[solve(len)][0]++;
			dfs2(x, 0, len);
		}
}
 
int main()
{
	IOS;
	int n; cin >> n;
	for (int i = 1, x; i <= n; i++)
	{
		cin >> x;
		v[i].push_back(x);
		v[x].push_back(i);
		deg[x]++; deg[i]++;
	}
 
	for(int i = 1; i <= n; i++)
		if (vis[i] == 0)
		{
			node.clear();
			node.push_back(i);
			dfs(i);
			bfs();
		} 
	
	for (int i = 1; i <= t; i++)
		for (int j = 1; j < N; j++)
			hoop[i][j] += hoop[i][j - 1];
 
	int q; cin >> q;
	while (q--)
	{
		ll x, y; cin >> x >> y;
		if (x == y) cout << n << endl;
		else
		{
			if (x > y) swap(x, y);
			int ans = 0;
			for (int i = 1; i <= t; i++)
				if ((y - x) % c[i] == 0)
					if (x < N) ans += hoop[i][x];
					else ans += hoop[i][N - 1];
			cout << ans << endl;
		}
	}
 
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

to cling

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值