1. 程式人生 > 實用技巧 >紀中暑假集訓 2020.08.07【NOIP提高組】模擬 T2:【佛山市選2013】樹環轉換

紀中暑假集訓 2020.08.07【NOIP提高組】模擬 T2:【佛山市選2013】樹環轉換

【佛山市選2013】樹環轉換

Description

給定一棵N個節點的樹,去掉這棵樹的一條邊需要消耗值1,為這個圖的兩個點加上一條邊也需要消耗值1。樹的節點編號從1開始。在這個問題中,你需要使用最小的消耗值(加邊和刪邊操作)將這棵樹轉化為環,不允許有重邊。

環的定義如下:

(1)該圖有N個點,N條邊。

(2)每個頂點的度數為2。

(3)任意兩點是可達的。

樹的定義如下:

(1)該圖有N個點,N-1條邊。

(2)任意兩點是可達的。

Input

第一行是一個整數N代表節點的個數。

接下來N-1行每行有兩個整數U, V(1 ≤ U, V ≤ N),表示雙向邊(U, V)

Output

輸出把樹轉化為環的最小消耗值。

Sample Input

4

1 2

2 3

2 4

Sample Output

3

Data Constraint

對於20%的資料,有1≤N≤10。

對於100%的資料,有1≤N≤1000000。

反思&題解

比賽思路: 忘了……
正解思路: 我們考慮貪心的思想:去掉多的邊補齊少的邊,很顯然最優肯定是他們一一對應,所以最後結果就是去掉的邊*2再加上樹比環少的那條邊。對於一個葉子節點,我們就不用去掉這條邊了;對於一個節點有兩個及以上個兒子的,就要去掉只剩1條(根節點的話可以剩2條),不過如果它一個兒子被去掉過邊,那這個兒子就不用算了。運用這種思想遍歷一棵樹統計答案就行了;
不過GMOJ的評測機會爆棧,所以要用人工棧或者BFS
(據說樹形DP才是最正的正解,不過貪心能過為啥要DP呢)
反思:

對於樹的各種性質還是要多瞭解

CODE

#include<bits/stdc++.h>
using namespace std;
struct arr
{
	int to,next;
}edge[2000005];
int n,head[2000005],cnt,ans,d[4000005],fa[2000005],tot[2000005];
bool bz[2000005],flag[2000005];
void add(int u,int v)
{
	edge[++cnt].to=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
int bfs()
{
	int h=0,t=1;
	d[1]=1;
	bz[1]=true;
	while (h<t)
	{
		int i,u=d[++h];
		for (i=head[u];i;i=edge[i].next)
		{
			int v=edge[i].to;
			if (v!=fa[u])
			{
				d[++t]=v;
				fa[v]=u;	
			}
		}
	}
	int i;
	for (i=t;i>=1;i--)
		printf("%d\n",d[i]);
	for (i=t;i>=1;i--)
		if (tot[d[i]]<2) tot[fa[d[i]]]++;
	memset(d,0,sizeof(d));
	h=0;
	t=1;
	d[1]=1;
	while (h<t)
	{
		int u=d[++h],i;
		if (tot[u]>=2)
		{
			ans+=tot[u]-1;
			if (u==1) ans--;
		}
		for (i=head[u];i;i=edge[i].next)
		{
			int v=edge[i].to;
			if (v!=fa[u]) d[++t]=v;
		}	
	}
}
int main()
{
	scanf("%d",&n);
	int i;
	for (i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	bfs();
	printf("%d\n",ans*2+1);
	return 0;
}