CF1833G Ksyusha and Chinchilla 题解

题目传送门

题意简介

在一棵树上删去一些边,使得形成的几个连通块,都有且仅有 $3$ 个结点。

多个答案时仅需输出任意一个。

每个测试点有多组测试数据。

解法

思路

首先注意到每个连通块都有且仅有 $3$ 个结点,所以节点数 $n$ 必须是 $3$ 的倍数。可以先特判。

考虑有解时,我们可以从下往上进行断边操作,可用 dfs 实现。 具体见 算法流程

算法流程

  • 记 $siz_i$ 为当前节点的子树中未被切断为单独的连通快的节点个数(有点绕)。换句话说,不经过断开的边所能到达的子树中的节点的个数。如下图,红色边为应断开的边,

花了我半小时

$i$ $1$ $2$ $3$ $4$ $5$ $6$ $7$ $8$ $9$
$siz_i$ $3$ $2$ $3$ $1$ $3$ $2$ $1$ $1$ $1$

可以自行理解一下。

  • 计算 $siz_i$:将各个调用的返回值累加。
  • $siz_i$ 可分以下情况:(记 $pre$ 为 $i$ 的父亲。)
    • 当 $siz_i=3$ 时,断开边 $(i,pre)$。同时返回 $0$ 表示没有未被切断的节点(都被切断了)。
    • 当 $siz_i<3$ 时,还不够一个连通块的节点数量 $3$,所以返回 $siz_i$。
    • 当 $siz_i>3$ 时,节点数量太多,输出 $\texttt{-1}$,结束。
  • 返回到主函数后,若返回值为 $0$,则有解,输出解。否则输出 $\texttt{-1}$,结束。

代码

#include <iostream>
#include <vector>
using namespace std;
struct edge
{
int v,id;
};
vector<edge> del;//记录删除的边
vector<edge> e[200001];//边表
int dfs(int now,int pre)
{
int siz=1;
edge tofa;
for(edge to:e[now])//遍历所有连边
{
if(to.v!=pre)
{
siz+=dfs(to.v,now);//累加返回值
}
else
{
tofa=to;//标记与父亲连的边
}
}
if(siz==3)//判断siz
{
if(pre!=-1)//特判,如果是根节点(1号),它没有父亲,不能删除与父亲连的边,不然会出错
{
del.push_back(tofa);
}
return 0;
}
else if(siz<3)
{
return siz;
}
else
{
// puts("-1");不能直接输出-1,因为不能退出程序,也无法直接回到main函数
return 0x3f3f3f3f;//让返回值极大,导致在主函数的特判中判定为无解
}
return 114514;//没用的,只是为了让编译器不报错
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
del.clear();//清除上一次的记录
// fill(vtoid,vtoid+n+1,0);
int n;
scanf("%d",&n);
int u,v;
if(n%3)
{
puts("-1");
for(int i=1;i<n;i++)
{
scanf("%d %d",&u,&v);
}
continue;
}
for(int i=1;i<=n;i++)
{
e[i].clear();
}
for(int i=1;i<n;i++)
{
scanf("%d %d",&u,&v);
e[u].push_back({v,i});
e[v].push_back({u,i});
}
if(!dfs(1,-1))//特判,如果返回值为0,则完美分配了所有节点。反之没有,就无解。
{
printf("%d\n",del.size());
for(edge d:del)
{
printf("%d ",d.id);
}
puts("");
}
else
{
puts("-1");
}
}
return 0;
}
posted @   Po7ed  阅读(6)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
点击右上角即可分享
微信分享提示