1. 程式人生 > >bzoj1956(一個快速簡單的方法)

bzoj1956(一個快速簡單的方法)

此題有樹,那麼能用的求最值的演算法有dp,貪心,流等等,如果我們以dp的思路來解題,觀察每顆子樹,那麼每顆子樹都相當於曼哈頓路徑的一部分鏈,子樹根可以在鏈的頭,尾,中間,其中根在頭和尾是等價的。那麼可以設計一個狀態dp[i][0]表示,以i為根的樹組成的一個從根i開始的鏈條,要花費的最小代價,dp[i][1]表示以i為根的樹組成的一個從非根節點開始的鏈條,注意兩個狀態表示的鏈的出點都是非根節點。那麼如果i是葉子節點,dp[i][0]=dp[i][1]=0;

現在考慮轉移:我們現在的目標是把各個子節點代表的鏈條,和根節點,一起串成一個鏈。

如果根節點選擇的子節點的狀態為dp[i][0]那麼不需要新加任何邊,直接用樹邊就好了,如果選擇的是dp[i][1]那麼新加一條邊。

我們現在需要討論的就是到底子節點是選擇用dp[i][0]好,還是選擇用dp[i][1]好,顯然如果我們對於每個子節點都能用dp[i][0]與dp[i][1]中的最小值,而且不加任何邊是最好的,但是由於用了最小值,那麼有可能就會新加一些邊,這樣就不一定好了

轉移的時候就是首先找出根節點的各個子節點的min(dp[子節點][0],dp[子節點][1])的和,再考慮如何將所有這些鏈條串成一個。我們儘量考慮多用樹邊,但是這會和我們前面求和矛盾,因為如果我選了dp[子節點][1]這個鏈條進行組合,是不能用樹邊的不是?那麼我前面的操作都是無用功了?當然不是。注意到,如果dp[子節點][1]<dp[子節點][0]那麼dp[子節點][1]<=dp[子節點][0]-1,也就是說,我們就算多加一條邊,最後得到的結果也是小於等於選擇dp[位元組點][0]來做鏈條的.有了這個性質就很好轉移了

minn表示最小值的和,cnt表示選擇的dp[子節點][0]的個數,size表示總的子節點數。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int first[150200], nextt[300400], too[300400];
int edgetot;
void addedge(int a, int b)
{
	too[edgetot] = b;
	nextt[edgetot] = first[a];
	first[a] = edgetot++;
	too[edgetot] = a;
	nextt[edgetot] = first[b];
	first[b] = edgetot++;
}
int n;
int dp[150200][2];
void com(int i, int &minn, int &cnt)
{
	if (dp[i][0] < dp[i][1])
	{
		minn += dp[i][0];
		cnt++;
	}
	else
	{
		if (dp[i][0] == dp[i][1])
		{
			minn += dp[i][0];
			cnt++;
		}
		else
			minn += dp[i][1];
	}
}
void dfs(int now, int fa)
{
	int cnt, minn, size;
	minn = 0; cnt = 0; size = 0;
	for (int i = first[now]; i != -1; i = nextt[i])
	{
		int to = too[i];
		if (to == fa)continue;
		dfs(to, now);
		com(to, minn, cnt);
		size++;
	}
	if (size == 0)
	{
		dp[now][0] = dp[now][1] = 0;
		return;
	}
	else
	{
		if (cnt >= 1)
		{
			dp[now][0] = min(dp[now][0], minn - 1+size);
		}
		else
		{
			dp[now][0] = min(dp[now][0], minn+size);
		}
		if (cnt >= 2)
		{
			dp[now][1] = min(dp[now][1], minn + size - 2);
		}
		else
		{
			if (cnt >= 1)
				dp[now][1] = min(dp[now][1], minn + size - 1);
			else
				dp[now][1] = min(dp[now][1], minn + size);
		}
	}
}
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		first[i] = -1;
		dp[i][0] = dp[i][1] = 1000000000;
	}
	for (int i = 0; i < n - 1; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		addedge(a, b);
	}
	dfs(1, 1);
	printf("%d\n",min(dp[1][0] + 1,dp[1][1]+1));
	return 0;
}