1. 程式人生 > 實用技巧 >Luogu - CF638C 【Road Improvement】 - 題解

Luogu - 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\n",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("\n");
	}
	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;
}