1. 程式人生 > 實用技巧 >USACO Cow at Large

USACO Cow at Large

USACO Cow at Large

洛谷傳送門

題目描述

最後,Bessie 被迫去了一個遠方的農場。這個農場包含 NN 個穀倉(2 \le N \le 10^52≤N≤105)和 N-1N−1 條連線兩個穀倉的雙向隧道,所以每兩個穀倉之間都有唯一的路徑。每個只與一條隧道相連的穀倉都是農場的出口。當早晨來臨的時候,Bessie 將在某個穀倉露面,然後試圖到達一個出口。

但當 Bessie 露面的時候,她的位置就會暴露。一些農民在那時將從不同的出口穀倉出發嘗試抓住 Bessie。農民和 Bessie 的移動速度相同(在每個單位時間內,每個農民都可以從一個穀倉移動到相鄰的一個穀倉,同時 Bessie 也可以這麼做)。農民們和Bessie 總是知道對方在哪裡。如果在任意時刻,某個農民和 Bessie 處於同一個穀倉或在穿過同一個隧道,農民就可以抓住 Bessie。反過來,如果 Bessie 在農民們抓住她之前到達一個出口穀倉,Bessie 就可以逃走。

Bessie 不確定她成功的機會,這取決於被僱傭的農民的數量。給定 Bessie 露面的穀倉K,幫助 Bessie 確定為了抓住她所需要的農民的最小數量。假定農民們會自己選擇最佳的方案來安排他們出發的出口穀倉。

輸入格式

輸入的第一行包含 NN 和 KK。接下來的 N – 1N–1 行,每行有兩個整數(在 1\sim N1∼N 範圍內)描述連線兩個穀倉的一條隧道。

輸出格式

輸出為了確保抓住 Bessie 所需的農民的最小數量。

由 @Marser 提供翻譯


題解:

2020.11.12模擬賽滿分場

%%%學弟TQL

一開始看到樹上最優化想樹形DP,後來發現推不出一個轉移方程。覺得是不是自己想假了?後來開始手玩樣例,發現一個性質:

按理講,每個葉子節點都應該安排人去堵,但是不必要,因為只要有多個人是等效的,就只派一個人去收即可。

什麼時候是等效的呢?

我們對於每個節點維護兩個資訊:第一個資訊是deep[x],牛最早能多早到達這個節點,其值就是深度-1。第二個資訊是low[x],最快到達這個節點的追的人最早能多早到達這個節點,其值可以深搜處理。很容易得出,如果low[x]<=deep[x],這個點就被堵死了,但是這個點的貢獻只是1,無論下面有多少葉子節點。

所以再深搜一遍,碰到堵死的節點就把ans++,再返回就可。

程式碼:

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
const int INF=1e9;
int n,s;
int tot,head[maxn],nxt[maxn<<1],to[maxn<<1];
int fa[maxn],deep[maxn],low[maxn];
int du[maxn];
int ans;
void add(int x,int y)
{
	to[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
void dfs1(int x,int f)
{
	deep[x]=deep[f]+1;
	fa[x]=f;
	int tmp=INF;
	for(int i=head[x];i;i=nxt[i])
	{
		int y=to[i];
		if(y==f)
			continue;
		dfs1(y,x);
		tmp=min(tmp,low[y]);
	}
	if(du[x]==1)
		low[x]=0;
	else
		low[x]=tmp+1;
}
void dfs2(int x)
{
	if(deep[x]>=low[x])
	{
		ans++;
		return;
	}
	for(int i=head[x];i;i=nxt[i])
	{
		int y=to[i];
		if(y==fa[x])
			continue;
		dfs2(y);
	}
}
int main()
{
	scanf("%d%d",&n,&s);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
		du[x]++,du[y]++;
	}
	deep[0]=-1;
	dfs1(s,0);
	dfs2(s);
	printf("%d\n",ans);
	return 0;
}