1. 程式人生 > 實用技巧 >UVA1218 完美的服務 Perfect Service 題解

UVA1218 完美的服務 Perfect Service 題解

原題連結:UVA1218 完美的服務 Perfect Service

題目大意

給定一個\(n\)個點的無向樹,現要在某些點上設立伺服器.每個不是伺服器的點必須只恰好跟一個是伺服器的點相連.問最少要放置幾個伺服器.

資料範圍
\(1 \leq n \leq 10000\)

注意正無窮不要設定0x3f3f3f3f,據說有類似菊花圖爆資料的.

思路

這是一個比較明顯而且套路的樹形DP的模型.先劃分一下狀態,這個題的限制條件是一個不是伺服器的點只能和一個伺服器連線,伺服器之間連幾個無所謂.那麼比較容易想到這樣的劃分:\(f[u][j]\)表示以\(u\)為根的子樹裡,\(j=0\)\(u\)是伺服器,他的兒子是不是無所謂;\(j=1\)

\(u\)不是伺服器,但是\(u\)的父節點是伺服器,那麼他的兒子必然不可能是伺服器;\(j=2\)表示\(u\)和他父節點都不是伺服器,但是如果他不存在父節點,那就不管,那麼\(u\)只有一個兒子是伺服器.

這裡一定要注意是如果有父節點才會要求他不是一個伺服器,如果他沒有父節點,但是本身也不是伺服器,那麼其實也是滿足定義的.而\(f[u][1]\)是強調有父節點,且必須是伺服器的,不然會和狀態矛盾.

入口:\(f[u][0] = 1\)是因為要設定自己是伺服器.\(f[u][1]=0\)\(f[u][2]=正無窮\)跟實際狀態轉移有關.
轉移:
\(f[u][0]\)
因為如果\(u\)是一個伺服器,那麼\(u\)

的兒子是不是伺服器是無所謂的,所以直接取較小的就可以了.\(f[u][0] = \sum\limits_{v\in subtree(u)} min(f[v][0],f[v][1]) + 1\).這裡的\(1\)一開始就加了,程式碼裡可以不寫.
\(f[u][1]\)
這個時候由於\(u\)不是伺服器,但是他的父節點是,又因為每個不是伺服器的點只能和一個伺服器相連,所以他的兒子\(v\)都不能是伺服器,也就是說,對於兒子此時的狀態其實就是\(f[v][2]\),自己不能選,父節點\(u\)也不能選,直接拿來用就可以了.\(f[u][1] = \sum\limits_{v\in subtree(u)} f[v][2]\)

\(f[u][2]\)
這個狀態的計算看起來就很奇怪,如果從定義出發的話計算會成平方級,也就是跟兒子的個數的平方級有關的複雜度.顯然是不行的,考慮從現有的狀態裡直接計算出這個狀態:因為現在是隻有一個\(v\)可以用,所以不妨先把所有的\(f[v][2]\)加起來,然後在裡面隨便摳一個\(v\),把他變成伺服器的代價算出來,實際上就是加一個\(f[v][0]\)再減去一個\(f[v][1]\).因為你要把一開始所有都沒選上的狀態裡那個\(f[v][2]\)給去掉,再加上他作為伺服器往下的代價.而且聯絡前面的內容,由於\(f[u][1]=\sum\limits_{v\in subtree(u)} f[v][2]\)所以這裡的表達是可以推出來是\(f[u][2] = {min}_{v\in subtree(u)}(f[u][1] - f[v][2] - f[v][0])\)
出口:答案一共有兩種,一種是\(f[root][0]\),一種是\(f[root][2]\),其中根root可以任選,兩種取最小值就可以了.沒有\(f[root][1]\)的原因是這種情況其實並不合法,因為對於這個\(root\)來說他的形態應該是沒有父節點的.

程式碼

#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 10005,M = 2 * N;
int edge[M],succ[M],ver[N],idx;
int n,f[N][3];
void add(int u,int v)
{
	edge[idx] = v;
	succ[idx] = ver[u];
	ver[u] = idx++;
}
void dfs(int u,int fa)
{
	f[u][0] = 1;f[u][2] = 10010;f[u][1] = 0;
	for(int i = ver[u];~i;i = succ[i])
	{
		int v = edge[i];
		if(v == fa)	continue;
		dfs(v,u);
		f[u][0] += min(f[v][0],f[v][1]);
		f[u][1] += f[v][2];
	}
	for(int i = ver[u];~i;i = succ[i])
	{
		int v = edge[i];
		if(v == fa)	continue;
		f[u][2] = min(f[u][2],f[u][1] + f[v][0] - f[v][2]);
	}
}
int main()
{
	while(scanf("%d",&n) == 1)
	{
		memset(ver,-1,sizeof ver);idx = 0;
		if(n == 0)	scanf("%d",&n);
		if(n == -1)	break;
		for(int i = 1;i < n;++i)
		{
			int u,v;scanf("%d%d",&u,&v);
			add(u,v),add(v,u);
		}
		dfs(1,-1);
		printf("%d\n",min(f[1][0],f[1][2]));
	}
    return 0;
}