【CF1039D】You Are Given a Tree

题目

题目链接:https://codeforces.com/contest/1039/problem/D
有一棵 \(n\) 个节点的树。其中一个简单路径的集合被称为 \(k\) 合法当且仅当树的每个节点至多属于其中一条路径,且每条路径恰好包含 \(k\) 个点。
对于 \(k\in [1,n]\),求出 \(k\) 合法路径集合的最多路径数。
即:设 \(k\) 合法路径集合为 \(S\),求最大的 \(|S|\)
\(n \leq 10^5\)

思路

考虑当 \(k\) 固定的时候怎么搞。设 \(f[x]\) 表示 \(x\) 子树内到 \(x\) 没有点用过的一条路径最长的长度。合并两棵子树时,如果 \(f[x]+f[y]+1\geq k\),那么就贪心合并这两条路径并让答案加一,\(f[x]\) 赋值为负无穷。
这样的贪心策略正确性显然,因为如果可以合并但是不合并而是把这一段长度给到父节点肯定不会更优。
暴力做是 \(O(n^2)\) 的。考虑根号分治。
设一个阈值 \(M\)。当 \(k\leq M\) 的时候暴力求。当 \(k>M\) 的时候,答案肯定在 \([0,\lfloor\frac{n}{M}\rfloor]\) 内,而随着 \(k\) 的增大,答案是不增的。
那么假设当前求 \(i\) 的答案 \(res\),我们可以二分一个右端点 \(j\) 满足它是最大的答案等于 \(res\) 的点。那么 \([i,j]\) 的答案一定都等于 \(res\)
二分次数是 \(O(\frac{n}{M})\) 的,每一次二分的复杂度都是 \(O(n\log n)\)。再加上前面暴力的复杂度,总时间复杂度为 \(O(nM+\frac{n^2\log n}{M})\)。取 \(M=\sqrt{n\log n}\) 时有最优复杂度 \(O(n\sqrt{n\log n})\)
这道题有点卡常,可以按照 dfs 序来循环 dp。

代码

#include <bits/stdc++.h>
using namespace std;

const int N=100010,M=1300,Inf=1e9;
int n,tot,head[N],dfn[N],fa[N],f[N];

struct edge
{
	int next,to;
}e[N*2];

void add(int from,int to)
{
	e[++tot]=(edge){head[from],to};
	head[from]=tot;
}

void dfs(int x,int fat)
{
	dfn[++tot]=x; fa[x]=fat;
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=fat) dfs(v,x);
	}
}

int solve(int k)
{
	int ans=0;
	for (int j=n;j>=1;j--)
	{
		int x=dfn[j]; f[x]=0;
		for (int i=head[x];~i;i=e[i].next)
		{
			int v=e[i].to;
			if (v!=fa[x])
			{
				if (f[v]+f[x]+1>=k) ans++,f[x]=-Inf;
				if (f[x]!=-Inf) f[x]=max(f[x],f[v]);
			}
		}
		f[x]++;
	}
	return ans;
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d",&n);
	for (int i=1,x,y;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y); add(y,x);
	}
	tot=0; dfs(1,0);
	cout<<n<<"\n";
	for (int i=2;i<=min(n,M);i++)
		cout<<solve(i)<<"\n";
	for (int i=M+1;i<=n;)
	{
		int l=i,r=n,mid,res=solve(i);		
		while (l<=r)
		{
			mid=(l+r)>>1;
			if (solve(mid)==res) l=mid+1;
				else r=mid-1;
		}
		for (;i<l;i++) cout<<res<<"\n";
	}
	return 0;
}
posted @ 2021-07-26 09:30  stoorz  阅读(65)  评论(0)    收藏  举报