#根号分治,树形dp#CF1039D You Are Given a Tree

题目

给定一棵树,对于 \(k\in [1,n]\) 问最多可以分成多少段长度为 \(k\) 的不交路径


分析

首先考虑对于单个 \(k\) 怎么做。

\(dp[x]\) 表示点 \(x\) 往下最多能伸出多长,那么 \(dp[x]=\max\{dp[y]+1\}\)

如果 \(dp[x]+dp[y]\geq k\) 那么标记 \(x\) 这个点不能再选,这样贪心显然是正确的。

但是 \(O(n^2)\) 显然是不能接受的,考虑大于 \(\sqrt{n}\) 的部分答案一定小于 \(\sqrt{n}\)

那可以在根号内直接树形dp,根号外由于产生了很多相同段,直接二分即可。

时间复杂度为 \(O(nT+\frac{n^2\log n}{T})\)\(T\)\(\sqrt{n}\log{n}\) 时最优。


代码

#include <cstdio>
#include <cctype>
#include <cmath>
using namespace std;
const int N=100011;
struct node{int y,next;}e[N<<1];
int qp[N],fat[N],as[N],et=1,dp[N],ans[N],n,Top,bl;
int iut(){
	int ans=0; char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
void print(int ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48); 
}
void Max(int &a,int b){a=a>b?a:b;}
void dfs(int x,int fa){
	for (int i=as[x];i;i=e[i].next)
	    if (e[i].y!=fa) dfs(e[i].y,x);
	qp[++Top]=x,fat[x]=fa;
}
int answ(int k){
	if (~ans[k]) return ans[k]; ans[k]=0;
	for (int i=1;i<=n;++i) dp[i]=1;
	for (int i=1;i<n;++i)
	if (dp[qp[i]]>0&&dp[fat[qp[i]]]>0){
		if (dp[fat[qp[i]]]+dp[qp[i]]>=k)
		    ++ans[k],dp[fat[qp[i]]]=-1;
		else Max(dp[fat[qp[i]]],dp[qp[i]]+1);
	}
	return ans[k];
}
int main(){
	n=iut(),bl=sqrt(n*log(n)/log(2));
	for (int i=1;i<=n;++i) ans[i]=-1;
	for (int i=1;i<n;++i){
		int x=iut(),y=iut();
		e[++et]=(node){y,as[x]},as[x]=et;
		e[++et]=(node){x,as[y]},as[y]=et;
	}
	dfs(1,0),ans[1]=n;
	for (int i=2;i<=bl;++i) ans[i]=answ(i);
	for (int l=bl+1;l<=n;++l){
		int _l=l,r=n,now=answ(l);
		while (l<r){
			int mid=(l+r+1)>>1;
			if (answ(mid)==now) l=mid;
			    else r=mid-1;
		}
		for (int j=_l;j<=l;++j) ans[j]=now;
	}
	for (int i=1;i<=n;++i) print(ans[i]),putchar(10);
    return 0;
}
posted @ 2021-11-16 19:14  lemondinosaur  阅读(45)  评论(0)    收藏  举报