AcWing 846. 树的重心 超详细解析

本文介绍了一种解决特定无向图问题的方法,即通过计算去除某个点后形成的连通块的最大点数,并寻找使得该值最小的点。文章详细解释了算法思路与实现过程,包括如何构建图数据结构、进行深度优先搜索以及优化计算策略。

在这里插入图片描述
https://www.acwing.com/problem/content/848/

首先要看清题目是 n个点 (n-1) 条边。

重心的定义是啥不多说,不懂的多看几次y总的视频,这不难理解。

题目给的是无向图,我们可以用有向图建两条边,来表示无向图。
题目给的点的范围是 小于1e5。

const int N=1e5+10;
const int M=N*2;
int h[N],e[M],ne[M],idx;
bool st[N];
int ans=N;

所以上面不难理解,因为是两条边故 e[ M ] , ne[ M ] ,头节点就是n个 h [ N ]

st[N]  是用来方式重复遍历的。 因为我们只需枚举每一个点一次就行了。
所以用其标记来保证每一个点只枚举一次

下面开始分析: 如何求去掉一个点后各个连通块点的个数。
在这里插入图片描述
你会发现: 取掉一个点后,此时有三个连通块(用蓝色标记)。

其实本质是两块内容

在这里插入图片描述

由上图可以看到: 本质分成了两个部分。

  • 第一部分: 以4为头节点的其每一个儿子的点的个数(我们取最大的)
  • 第二部分: 4作为一个子节点求其所对应的连通块的点数.

那么4作为子节点的点数= n(总的点数) - 4的儿子的节点数之和。

到这里我想各位应该已经懂了一大半了。
下面看代码分析:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5+10;
const int M=N*2;
int n;
int h[N],e[M],ne[M],idx;
bool st[N];
int ans=N;
void add(int a,int b)//向以a头节点 加入b
{
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dfs(int u)//求以 u为头节点的子节点的点数之和
{
	st[u]=true;
	int sum=1,res=0;  //sum=1  是因为加上去掉的那个数
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(!st[j])
		{
			int s=dfs(j);
			res=max(res,s);//  求子节点中点数最大的
			sum+=s; //求以j为头节点的子节点的点数之和
		} 
	}
	
	res=max(res,n-sum);//求头节点所在连通块  和 其子节点最大的点数 中最大的值
	ans=min(ans,res);//求我们每一种情况中,最小的值
	
	return sum;
}
int main(void)
{
	cin>>n;
	memset(h,-1,sizeof h);
	for(int i=0;i<n-1;i++)
	{
		int a,b; cin>>a>>b;
		add(a,b),add(b,a);
	} 
	dfs(4);
	cout<<ans<<endl;
	return 0;
}

首先 因为我这里举的例子是从4开始的,故写的dfs(4) 代表从 4开始枚举。
其实 dfs() 从1到n中的任何一个数枚举都是一样的,因为我们都是只枚举一遍。
不能从0开始是因为我们的节点是从1开始的。你不能枚举一个没有的点。

刚开始:
在这里插入图片描述
刚开始 4可以到的有 1 3 6
然后1 没有走过,就求去掉1后的最大点数 , 1又可以到达 2 7 以此递归。
3
6
在这里插入图片描述
我想 st[ N ] 的作用大家都知道了吧。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5*2+10;
int h[N],e[N],ne[N],idx,n,ans=N;
bool vis[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dfs(int u)
{
    vis[u]=1;
    int res=0;
    int sum=1;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!vis[j])
        {
            int t=dfs(j);
            res=max(res,t);
            sum+=t;
        }
    }
    res=max(res,n-sum);
    ans=min(ans,res);
    return sum;
}
int main(void)
{
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n-1;i++)
    {
        int a,b; cin>>a>>b;
        add(a,b),add(b,a);
    }
    dfs(1);
    cout<<ans;
}
### 问题分析 Acwing 4925. 干衣机 是一道典型的二分答案题目,主要涉及对时间的优化判断。题意大致如下: - 给定一个洗衣机,它在每单位时间可以处理一件衣服。 - 每件衣服有一个初始湿度值。 - 在每一单位时间中,可以选择一件衣服将其放入洗衣机中脱水(湿度减少1),其余衣服的湿度也会自然减少1(但不能低于0)。 - 要求计算将所有衣服的湿度降到0所需的最短时间。 为了解决这个问题,需要结合贪心策略与二分查找技术来优化时间复杂度。 ### 解题思路 #### 1. 二分查找时间范围 由于目标是找到“最短时间”,可以采用二分法来尝试不同的时间值 `t`,并验证是否可以在该时间内完成任务。 - 最小时间为 `max(a)`(即所有衣服中最高峰值,若不考虑其他衣服自然干燥的情况) - 最大时间上限可以设定为最大湿度值加上衣服总数的影响。 #### 2. 验证函数设计 对于给定的时间 `t`,我们需要判断是否能在此时间内使所有衣服湿度归零。 具体来说: - 对于某件衣服湿度 `a[i]`,如果 `a[i] > t`,则必须至少手动操作 `a[i] - t` 次才能让其湿度降为0。 - 所有这些操作次数之和应小于等于 `t`,因为每单位时间只能操作一次。 #### 3. 实现代码 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; bool check(const vector<int>& a, int t) { long long need = 0; for (int x : a) { if (x > t) { need += x - t; } } return need <= t; } int main() { int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; ++i) { cin >> a[i]; } int left = 0, right = 1e9; while (left < right) { int mid = (left + right) / 2; if (check(a, mid)) { right = mid; } else { left = mid + 1; } } cout << left << endl; return 0; } ``` ### 复杂度分析 - 时间复杂度:`O(n log M)`,其中 `n` 是衣服数量,`M` 是最大可能时间上限(约 `1e9`)。 - 空间复杂度:`O(1)`,仅使用常数额外空间。 ### 总结 本题通过二分法快速逼近最优解,并在每次判断中使用线性扫描验证可行性,是一种高效且常见的算法组合应用。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值