题解:CF2131D Arboris Contractio
题意
给定一棵无根树
- 选择两个节点
s,t\in T 。 - 把
s 到t 的简单路径上的每一个节点,断掉原来位于这条链上的边,直接连到s 节点上
问最少多少次操作才能让
思路
我们注意到这样一个性质:
对于一个选出的根节点
然而,一次操作只能改变一个叶子节点,因为我们必须设定
这说明什么?
这说明我们只需要操心叶子节点。
更进一步,操作次数就是需要动的叶子节点个数,也就是不直连
问题来了,对于每一个
正难则反,我们只需要先从任意一点出发,求总叶子节点个数,然后对于每一个
设以
所以我们只要对每一个节点算出答案,再求
复杂度
时间复杂度
我们考虑预处理出总叶子节点个数,以及每一个节点直连叶子节点个数,这是
对于每一个节点,算该节点为根答案所需的两个参数我们已经计算好,就是
总共
空间复杂度
我们的信息都是对于每一个节点存的,有
实现
通过分开 dfs 来进行预处理和计算每一个节点的答案。
在预处理 dfs 完了之后,需要判一下目前作根的点是否只有一个子节点,如果是,总叶子节点个数要
注意,特判
另外,减少时间消耗小技巧:在判断是否为叶子节点时,不需要算完有多少子节点,大于某个值直接返回就行了,因为不是叶子,这也确保了这个操作是
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct edge
{
int to,nxt;
} e[500010];
int head[500010],cnt,ans,son,sl[500050],num[500010];
inline void add(int u,int v)
{
cnt++;
e[cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
int check(int u,int f)
{
int sum=0;
for(int i=head[u];i!=0&&sum<=1;i=e[i].nxt)
{
int v=e[i].to;
if(f!=v) sum++;
}
return sum;
}
void dfs1(int u,int f)
{
num[u]=1;
sl[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v!=f)
{
num[u]++;
if(!check(v,u)) sl[u]++,son++;
dfs1(v,u);
}
}
}
void dfs2(int u,int f)
{
ans=max(ans,sl[u]);
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v!=f) dfs2(v,u);
}
}
signed main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
cnt=ans=son=0;
for(int i=0;i<=2*n;i++) head[i]=0,e[i]={0,0},num[i]=0,sl[i]=0;
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
if(n==2) {cout<<"0\n";continue;}
dfs1(1,0);
if(check(1,0)==1)
{
son++;
for(int i=head[1];i;i=e[i].nxt)
{
int v=e[i].to;
if(v!=0) sl[v]++;
}
}
dfs2(1,0);
cout<<son-ans<<endl;
}
return 0;
}
完结撒花!
碎碎念:机房某位大佬看到是树的题就跳了,但是水到我这个蒟蒻都可以过