CF638C 【Road Improvement】

题目传送门


这是一道贪心题。

  • 求出 (k) 其实很简单,因为每个节点的边每天最多只能修一条,所以答案就是度数最大的节点的度数(也就是边最多的节点的边数)。
  • 重点在于怎么求方案。
    1. 因为题目中的树是一颗无根树,所以我们默认 (1) 号节点为他的根节点。
    2. 接下来我们开 (k) 个集合,表示第 (i) 天要修的路的编号(这里可以用 vector 实现)。
    3. 划重点 然后开始从 (1) 号节点 dfs ,每走到一个节点,就把 它与它父亲之间的路它与所有儿子间的路 放在不同的集合中,因为这些边的修筑 必须都有该节点参与 ,但是由于该节点 一天只能修一条路 ,所以要放在不同天修。
    4. 就这样遍历完整棵树,然后按要求输出就可以了。

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;
}
原文地址:https://www.cnblogs.com/blackbird137/p/13550353.html