1. 程式人生 > 實用技巧 >CF1187E Tree Painting

CF1187E Tree Painting

前言

一道挺好的思維題。

本人蒟蒻一枚,這題是本人獨立A掉的第一道洛谷藍色難度的CF思維題,極具紀念意義。故本人會將思考過程儘量全面地記錄下來,以觀察思考的不足之處。

題意簡述

題目連結

  給定一棵樹,初始時全為白點,要求按以下方法進行n次染色操作:

  1.第一次可以任意選擇一個節點染成黑色。

  2.以後每一次任意選擇一個與黑點有直接邊相連的白點,將其染成黑色。

  定義每次染色可獲得的權值為該次染色時,所染的白點所在連通塊的大小。

  要求n次染色後所獲得的權值之和最大,求這個最大值。

演算法概述

  最優化問題,優先考慮DP。

  首先很容易發現一個很顯然的性質:當第一次染色的節點確定下來之後,以後染色所能獲得的權值之和就確定了。

  暴力列舉第一次染色的節點顯然不行,所以可以考慮用dp來計算這個權值之和。

  f[u]表示以u為一號染色點時所能獲得的權值之和。

  我們關鍵來看這個f[u]如何計算。不難發現f[u]可分成兩部分:

  ①從u出發向以u為根的子樹染色,所能獲得的權值之和。

  ②從u出發向u的父親方向染色,所能獲得的權值之和。

  子樹的資訊還是比較方便統計的,主要難點在於第②部分,首先顯然u也是在其父節點的子樹當中的,所以我們發現一個點的子樹資訊可能要重複被用到,故我們再開一個數組。

  dp[u]表示從u出發向以u為根的子樹染色,所能獲得的權值之和。要計算dp[u],只需考慮其每個兒子即可。

  顯然dp[u]=∑(dp[v]+siz[v]),其中v為u的兒子節點,siz[u]表示以節點u為根的子樹大小。

  所以我們可以自底向上計算出每個點的dp值。

  那麼f[u]的第①部分就是dp[u]了。

  再來看第②部分,記u的父節點為fa,考慮f[fa]的組成:一部分是向u染色,另一部分是向其他節點染色。

  所以我們可以在f[fa]中挖掉向u染色的部分,然後再加上從u到fa這一步的值(即將fa這個節點染色的權值)即可。形式化來說,挖掉向u染色的部分即f[fa]-dp[u]-siz[u],再加上從u到fa這一步的值即n-siz[u](總節點數減去子樹u的大小)。

  故第②部分的值就等於f[fa]-dp[u]+n-2*siz[u]。

  於是我們就得到了f的狀態轉移方程:f[u]=dp[u]+f[fa]-dp[u]+n-2*siz[u]=f[fa]+n-2*siz[u]。

  所以我們可以自上往下計算出每個點的f值。

  最後再比較得出全域性最大值即可。

  當然,由於我們計算f[u]時並未加上第一步染色u點時的權值,故而最後還應將答案加上n。

  時間複雜度O(n+m)

  根據如上分析,我們發現了最開始思考時的思維漏洞——dp陣列其實根本不需要。

  我們只需要先預處理出以1為根時的權值之和,然後以此為邊界,根據上面的狀態轉移方程再在dfs過程中進行遞推即可。

  然後我們重新考慮一下f[u]的計算:

  考慮f[fa]的組成:(1)一部分是向u染色,(2)另一部分是向其他節點染色。

  考慮f[u]的組成:(3)一部分是向fa染色,(4)另一部分是向子樹染色。

  我們發現(4)是包含在(1)中的,(4)=(1)-siz[u],而(2)是包含在(3)中的,(3)=(2)+n-siz[u]。

  兩式一加即可直接得出結果。

  最後這題答案比較大,可能會爆int,故需要開long long。

參考程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=2e5+10;
struct Edge{
	int to,next;
}edge[N<<1];int idx;
int h[N];

void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}

ll f[N];
int siz[N];
int n;

ll dfs1(int p,int fa)
{
	ll res=0; //res的值即為dp值 
	siz[p]=1;
	for(int i=h[p];~i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(to==fa)continue;
		res+=dfs1(to,p)+siz[to];
		siz[p]+=siz[to];
	}
	return res;
}

void dfs2(int p,int fa)
{
	for(int i=h[p];~i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(to==fa)continue;
		f[to]=f[p]+n-2*siz[to];
		dfs2(to,p);
	}
}

int main()
{
	memset(h,-1,sizeof h);
	scanf("%d",&n);
	for(int i=1;i<=n-1;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	
	f[1]=dfs1(1,0);
	dfs2(1,0);
	
	ll ans=0;
	for(int i=1;i<=n;i++)ans=max(ans,f[i]);
	printf("%lld\n",ans+n);
	
	return 0;
}