题目大意:有一棵n个点的树,要求找到一个最大的x,使得在树中移除恰好k个边后,树中剩余的每一部分都至少有x个点。
1<=k<n<1e5
思路:因为总的点数是n,是定值,所以x越大,那么按最优方法操作移除的边数就越少,反之x越小,需要移除的边数就越多,很明显可以利用这个单调性二分x。
那么接下来考虑在确定了某个x的情况下求需要移除的最少边数,因为按照上面的单调性,移除的边数越少x越大。
那么很容易想到的就是进行dfs,后序维护当前点u的子树大小siz[u],如果siz[u]>=x,那么这个点和它父节点之间的边就要断掉,使用的边的数量tot+1,然后令siz[u]=0,使其不影响后序的dfs过程,但如果当前点时根节点也就是1号点,它没有父节点,所以tot不能+1,而且如果以1为根的子树的大小<x,那么之前断掉的一个边还要连回去,使得该子树大小满足>=x。
如果得到的tot>=k,那么说明x还可增大,反之要减小x
#include<__msvc_all_public_headers.hpp>
//#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
const ll MOD = 1e9 + 7;
ll n;
vector<int>g[N];
int siz[N];
ll k;
int tot = 0;
ll qpow(ll a, ll b)
{//快速幂
a %= MOD;
ll ret = 1;
while (b)
{
if (b & 1)
{
ret = ret * a % MOD;
}
a = a * a % MOD;
b >>= 1;
}
return ret;
}
void init()
{
for (int i = 1; i <= n; i++)
{
g[i].clear();
siz[i] = 1;
}
}
void dfs(int u, int fa, int x)
{
for (const auto& i:g[u])
{
int v = g[u][i];
if (v == fa)
{
continue;
}
dfs(v, u, x);
siz[u] += siz[v];//维护子树大小
}
if (u != 1 && siz[u] >= x)
{//当前子树的点数>=x,可以断开父边
tot++;
siz[u] = 0;
}
if (u == 1 && siz[u] < x)
{//以1为根且子树大小<x,要连回去一条边
tot--;
}
}
void solve()
{
cin >> n;
init();
cin >> k;
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
int l = 1, r = n, mid;
int ans;
while (l <= r)
{//二分x
mid = (l + r) >> 1;
tot = 0;
for (int i = 1; i <= n; i++)
{
siz[i] = 1;
}
dfs(1, 0, mid);
if (tot >= k)
{
ans = mid;
l = mid + 1;
}
else
r = mid - 1;
}
cout << ans;
cout << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin >> t;
//t = 1;
while (t--)
{
solve();
}
return 0;
}