CF1039D You Are Given a Tree
题目大意
给出一棵$ 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; }
不要忘记点个赞哦
完结撒花