題目大意
給出一棵$ n $個節點的樹,對於$ 1 $~$ n $間的每一個數$ k $,你需要求出: 最多能選出多少條互不相交的路徑,使得每條路徑的長度都為$ k $。
思路
首先思考暴力的做法。就是貪心+樹形dp,把整個樹$ dfs $一次,對於每個節點考慮它子節點的最長鏈和次長鏈。如果最長鏈+次長鏈+$ 1 \geq k $,那么就說明它子節點的最長鏈和次長鏈通過那個子節點連起來形成的鏈可以滿足長度大於等於$ k $,答案+$ 1 $。
那么為什么滿足條件就一定要先把它們連起來呢?因為由於當前存在了一條滿足要求的鏈,如果不計入答案,把最長鏈往上傳,那么最多對答案也只有$ 1 $的貢獻。所以先連起來可能會更優。這樣時間復雜度就是$ O(n^2) $。
之后就用分治優化。設分治節點為t,對於第一段直接用暴力求解,時間復雜度就是$ O(nt) $,對於第二段,因為答案只會在$ 0 $~$ \frac{n}{t} $這個范圍內,而且是單調不升的,相同答案的k值肯定是連在一起的,所以可以考慮從左往右掃一遍,每次用二分出答案相同的一段,這樣時間最多也就$ \frac{n^2\log(n)}{t} $ 。所以當t取$ \sqrt[]{n\log(n)} $時,時間復雜度最小,為$ O(n\sqrt[]{n\log(n)}) $。
當然,$ t $取$ \sqrt n $會更容易理解,雖然時間沒有$ t $=$ \frac{n^2\log(n)}{t} $ 快,但只要我們優化一下,去掉$ dfs $的過程,記錄$ dfs $序,通過$ for $循環以達到$ dfs $的目的,也是可以過的。
所以我也把這個小優化也加到了我的代碼里。
代碼

#include<bits/stdc++.h> using namespace std; int n,q,x,y,head[200002],Fa[100001],dfsx[100001],f[100001],cnt=0,tot=0,t,l,r,mid; struct node{ int nxt,to; }e[200002]; void add(int x,int y){ e[++cnt].nxt=head[x]; e[cnt].to=y; head[x]=cnt; } void dfs(int x,int fa){ Fa[x]=fa; for(int i=head[x];i;i=e[i].nxt){ int v=e[i].to; if(v!=fa) dfs(v,x); } dfsx[++tot]=x; return; } int solve(int x){ int k,ans=0; for(int i=1;i<=n;i++) f[i]=1; for(int i=1;i<=n;i++){ int k=dfsx[i]; if(Fa[k]&&~f[Fa[k]]&&~f[k]){ if(f[k]+f[Fa[k]]>=x){ f[Fa[k]]=-1; ans++; } else f[Fa[k]]=max(f[Fa[k]],f[k]+1); } } return ans; } int main(){ scanf("%d",&n); q=sqrt(n*log(n)/log(2)); for(int i=1;i<n;i++){ scanf("%d %d",&x,&y); add(x,y); add(y,x); } dfs(1,0); printf("%d\n",n); for(int i=2;i<=q;i++) printf("%d\n",solve(i)); for(int i=q+1;i<=n;i=l+1){ l=i; r=n; t=solve(i); while(l<r){ mid=(l+r+1)>>1; if(solve(mid)==t) l=mid; else r=mid-1; } for(int j=i;j<=l;j++) printf("%d\n",t); } return 0; }
不要忘記點個贊哦
完結撒花