CF 1042F
玄學貪心...
題意:給出一棵樹,要求將他的所有葉節點分成最少的組,且在每組中的任意兩節點之間的距離不大於k
解析:
顯然是個貪心啦...
稍微考慮一下貪心思想:
我們從下向上合併整棵樹,在合併到某個節點時,我們把他以下的所有葉節點到他的距離全處理出來然後排序,設所有距離排序後為d1,d2...dn
接下來,我們從大到小列舉每個d,如果滿足di+di-1+2<=k,那麼說明從1到i的所有子節點都可以分到一組裡面,而剩下的節點只能單獨一個分進一組裡了
然後我們向上返回不單獨分組的點中距離最大的一個的距離,向上回溯合併即可
畫圖理解一下:
如圖所示,這是一棵樹,設k=2
首先我們合併5,6兩個點,那麼發現這兩個點到根的距離都是1,於是我們開心地將這兩個點合併到一個集合裡,然後回溯
其餘最底層節點同理
這是合併前葉節點的狀態
這是合併上去第一層的狀態
繼續合併一層:
於是我們可以繼續開心地合併,這時可以發現,所有的次二層節點都無法再合併,這時合併結束,一共需要三個集合
可能這一點不是很好理解,所以我們再假設k=4,那麼就可以把2和4合併起來,而3還是無法合併,這樣就只需要兩個集合了。
總結一下思路:將一個根節點以下的所有葉節點到根節點的距離排序,然後倒序列舉,對於所有的di+di-1+2>=k的部分,我們都要把di單獨分組,而剩餘的部分可以分進一組,將這一組中的葉節點到這個根節點最長的距離回溯到上一層即可。
證明貪心的正確性:
首先,剩餘部分可以分成一組是顯而易見的,因為不會有比這更優的方案
而其他的點需要單獨分組的原因:假設這些點可以與另一部分子樹中的某一部分合併成一個集合,那麼根據距離的關係可知,剩下的分成一組的點一定也可以和那一部分合併成一個集合,但這兩者卻不能合為一個集合,所以無論怎麼合併都至少需要兩個集合,也就是這樣分組一定是某一種最優方案!
這樣就完事了
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #include <vector> using namespace std; struct Edge { int next; int to; }edge[2000005]; int head[1000005]; int inr[1000005]; int cnt=1; int n,k; void init() { memset(head,-1,sizeof(head)); cnt=1; } void add(int l,int r) { edge[cnt].next=head[l]; edge[cnt].to=r; head[l]=cnt++; } int cct=0; int dfs(int x,int fx) { vector<int>v; for(int i=head[x];i!=-1;i=edge[i].next) { int to=edge[i].to; if(to==fx) { continue; } v.push_back(dfs(to,x)+1); } if(v.size()==0) { return 0; } sort(v.begin(),v.end()); int i; for(i=v.size()-1;i>=1;i--) { if(v[i]+v[i-1]<=k) { break; } cct++; } return v[i]; } int main() { scanf("%d%d",&n,&k); init(); for(int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); add(x,y); add(y,x); inr[x]++; inr[y]++; } int rot=0; for(int i=1;i<=n;i++) { if(!rot&&inr[i]!=1) { rot=i; break; } } dfs(rot,rot); printf("%d\n",cct+1); return 0; }