题解【CF1695D2 Tree Queries (Hard Version)】
BqtMtsZDnlpsT · · 题解
分享一个做法。
题意:
给你一棵树,你可以询问一些点
问至少询问多少个节点,可以在神秘节点是任意节点时,将其推断出来。
以下
首先我们考虑选了两个点可以知道那些信息:
假设我们当前选了
那么如果
所以,
我们可以通过不断将
再通过减了几次
如上图若
若我们已经选了两个点,那么什么情况下不能问出全部神秘节点?
我们发现,这两个点确定了这条路径上所有点,因此我们可以将这条路径上的所有边看做“虚化”了,一条边虚化后,不计入度数。若此时有一个点度数
如图,如果我们仅仅是知道了走到红色节点
利用虚化的思想拓广:三个点时会怎么样,仔细思考后就会发现,虚化的边集即为三个点两两间路径边集的并,也就是说,把三个点两两间路径上的边全部虚化。
在虚化过程中,选择度为
具体的,对于一个度为
删完度为
贪心选择,选择新树上所有度为
至于最后剩下的附属点,那只能暴力选了,注意可以留一个不选。
复杂度
代码:
//赛时的,由于一些原因可能有一些神秘操作
//其实不是赛时的,赛时的代码由于两个小问题 wa on 2了
int n,m,d[200005],vis[200005];
set<int>E[200005];
inline void work(){
for(int i=1;i<=n;i++){
E[i]=set<int>();
d[i]=vis[i]=0;
}
n=read();
if(n==1)return pus("0");
for(int i=1;i<n;i++){
int x=read(),y=read();
E[x].insert(y);E[y].insert(x);
}
if(n==2)return pus("1");
for(int i=1;i<=n;i++){
if(E[i].size()==2){//删度为 2 的点
int u=*E[i].begin(),v=*--E[i].end();
vis[i]=1;
E[u].erase(i);E[v].erase(i);
E[u].insert(v);E[v].insert(u);
}
}
for(int i=1;i<=n;i++)d[i]=E[i].size();
int cnt=0;
for(int i=1;i<=n;i++){
cnt+=(E[i].size()==1);
if(E[i].size()==1)vis[i]=2;
}
if(cnt==2)return pus("1");
for(int i=1;i<=n;i++){
if(vis[i]==2){//找新树
vis[i]=1;
int u=*E[i].begin();
E[u].erase(i);E[i].erase(u);
}
}
int ans=0;
for(int i=1;i<=n;i++){
if(E[i].size()==1){d[i]-=2;ans++;}
else d[i]-=(int)E[i].size();
//如果在新树上度为 1,那就是其中一条上出边,与一条连到附属点的边虚化
//否则减新树上所有出边
if(!vis[i])ans+=max(d[i]-1,0);//还有剩下的附属点
}
write(ans,'\n');
}