这是一道贪心题。
- 求出 (k) 其实很简单,因为每个节点的边每天最多只能修一条,所以答案就是度数最大的节点的度数(也就是边最多的节点的边数)。
- 重点在于怎么求方案。
- 因为题目中的树是一颗无根树,所以我们默认 (1) 号节点为他的根节点。
- 接下来我们开 (k) 个集合,表示第 (i) 天要修的路的编号(这里可以用 vector 实现)。
划重点然后开始从 (1) 号节点 dfs ,每走到一个节点,就把 它与它父亲之间的路 与 它与所有儿子间的路 放在不同的集合中,因为这些边的修筑 必须都有该节点参与 ,但是由于该节点 一天只能修一条路 ,所以要放在不同天修。- 就这样遍历完整棵树,然后按要求输出就可以了。
Code:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<bitset>
#include<cmath>
#include<queue>
#include<map>
#include<set>
using namespace std;
int read()
{
int ans=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();}
return ans*f;
}
const int N=2e5+5;
int n,du[N],head[N],tot,ans;
//du 用来统计节点度数
vector<int> agg[N];
//用 vector 模拟集合
struct Edge
{
int v,ne;
}e[N*2];
//这里要开双倍空间,因为是双向边
inline void add(int u,int v);
//加边函数
inline void dfs(int u,int fa,int last);
//dfs 求方案
int main()
{
n=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
du[u]++,du[v]++;//统计度数
add(u,v);
add(v,u);
ans=max(ans,max(du[u],du[v]));
//取度数最大值为答案
}
printf("%d
",ans);
//先输出答案
dfs(1,-1,0);
//然后 dfs 求方案
for(int i=1;i<=ans;++i)
{
printf("%d ",agg[i].size());
//先输出这一天修的路的数量
for(int j=0;j<agg[i].size();++j)
printf("%d ",agg[i][j]); //然后输出具体的路的编号
printf("
");
}
return 0;
}
inline void add(int u,int v)
{
e[++tot]=(Edge){v,head[u]};
head[u]=tot;
}
//u 表示当前节点,fa 表示 u 节点的父亲,last 表示 u 与 fa 间的路所在的集合编号
inline void dfs(int u,int fa,int last)
{
int j=1; // j 表示当前枚举到的集合数
for(int i=head[u];i;i=e[i].ne)
{
int v=e[i].v;
if(v==fa) //判断一下,别走回头路
continue;
if(j==last) //如果当前集合编号是 u 与它父亲间的路所在集合的编号
++j; //那么这个集合不能放这条边,及 ++j;
agg[j].push_back((i+1)/2);
//然后记录答案
//因为上面存的是双向边,所以不能直接存 i
//考虑到同一条边在数组中的下表是 x,x+1
//所以通过 (x+1)/2 可以计算出该边在读入时的编号。
dfs(v,u,j);
//继续递归该节点
++j;
//因为该集合已经放了边,所以 j 要 +1
}
return;
}