1. 程式人生 > >JZOJ5944. 【NOIP2018模擬11.01】信標

JZOJ5944. 【NOIP2018模擬11.01】信標

題意:

資料範圍:

對於前 20 20 % 的資料, n 10 n \leq 10 ;
對於前 45

45 % 的資料, n 40 n \leq 40 , 樹的形態隨機;
對於前 70
70
% 的資料, n 5000 n \leq 5000 ;
對於另 5 5
% 的資料, 不存在一個村莊連線著 3 3 條或以上的道路;
對於 100 100 % 的資料, 1 n 1000000 1 \leq n \leq 1000000 , 1 u , v n 1 \leq u, v \leq n , 保證資料合法.

Analysis:

又得上一堆結論,考場就只個推出放葉子節點最優,還是趕緊收拾收拾滾回家吧,考什麼NOIP。
首先假如以一個節點為根,放一個信標在上面,那麼限制只會在相同深度之間,相當於給樹分層了。
考慮怎麼放最優,如果我把一個信標放在一個分叉的上面,我可以把這個信標拖到分叉處,使得更多的節點被覆蓋,限制被解決,那麼最後肯定會有一條鏈往下,我們可以把信標拖到葉子節點處,這樣不會解決上下對稱,答案不會更劣,所以放葉子節點最優。
對於一個節點,它會成為若干深度相同節點的LCA,若一個信標不放到這些節點對所處子樹,那麼他們就會不合法(先走到LCA然後往下走)。對於一個點,我們就可以發現,假如他有 c c 個兒子,那麼我們就要放 c 1 c-1 個信標在不同的兒子裡,使得覆蓋所有限制。
但這個策略需要列舉根,放信標在上面來分層,考慮在什麼情況下不在根放信標成立。
發現只要根的度數大於等於 3 3 即可。考慮深度不同的如何解決,若一對點處在同一子樹,顯然可以放一個信標在另一個子樹解決限制。若處在不同子樹,那麼該對點按以上策略,肯定有一個點子樹內有信標,我們貪心地將信標下放到了葉子節點,那麼肯定可以將其區分開。
那麼按照以上策略貪心即可。複雜度 O ( n ) O(n)

Code:

# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
const int N = 1e6 + 5;
int st[N],to[N << 1],nx[N << 1];
int rd[N],sz[N];
int n,tot,rt,ans;
inline void add(int u,int v)
{
	to[++tot] = v,nx[tot] = st[u],st[u] = tot;
	to[++tot] = u,nx[tot] = st[v],st[v] = tot;
}
inline void dfs(int x,int fr)
{
	int cnt = 0;
	for (int i = st[x] ; i ; i = nx[i])
	if (to[i] != fr) dfs(to[i],x),++cnt;
	if (cnt > 1) sz[x] = 1;
	for (int i = st[x] ; i ; i = nx[i])
	if (to[i] != fr && sz[to[i]]) --cnt,sz[x] = 1;
	if (cnt) ans += cnt - 1;
}
int main()
{
	freopen("beacon.in","r",stdin);
	freopen("beacon.out","w",stdout);
	scanf("%d",&n);
	if (n == 1) { puts("0"); return 0; }
	for (int i = 1 ; i < n ; ++i)
	{
		int u,v; scanf("%d%d",&u,&v);
		add(u,v),++rd[u],++rd[v];
	}
	for (int i = 1 ; i <= n ; ++i) if (rd[i] > 2) rt = i;
	if (!rt) { puts("1"); return 0; }
	dfs(rt,0);
	printf("%d\n",ans);
	return 0;
}