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; }