[CF1039D] You Are Given a Tree

问题简述

有一棵n个节点的树
其中一个简单路径的集合被称为k合法当且仅当:
树的每个节点至多属于其中一条路径,且每条路径恰好包含k个点
对于k∈[1,n],求出k合法路径集合的最多路径数
即:设k合法路径集合为S,求最大的|S|

样例输入

6
1 2
2 3
2 4
1 5
5 6

样例输出

6
2
2
1
1
0

说明

样例如下

img

解析

首先,\(O(n^2)\)的暴力做法可以用贪心,对于每一棵子树,能够在子树内拼成长度为k的链就拼,返回剩下的最长的链到父节点去继续拼。

考虑如何优化。这里用到了根号分治的方法,当k小于等于\(\sqrt n\)的时候,就直接暴力去做;当k大于\(\sqrt n\)时,可以发现对于某一段连续的k,答案是一样的,所以我们二分出这个一样的区间,然后把这个区间的答案都赋为当前的答案。具体实现可以参考代码。

代码

#include <iostream>
#include <cstdio>
#include <cmath>
#define N 100002
using namespace std;
int head[N],ver[N*2],nxt[N*2],l;
int n,i,j,f[N],ans[N],top,s[N],fa[N];
int read()
{
    char c=getchar();
    int w=0;
    while(c<'0'||c>'9') c=getchar();
    while(c<='9'&&c>='0'){
        w=w*10+c-'0';
        c=getchar();
    }
    return w;
}
void insert(int x,int y)
{
    l++;
    ver[l]=y;
    nxt[l]=head[x];
    head[x]=l;
}
void dfs(int x,int pre)
{
    fa[x]=pre;
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y!=pre) dfs(y,x);
    }
    s[++top]=x;
}
int dp(int k)
{
    int ans=0,f[N];
    f[0]=-1;
    for(int i=1;i<=n;i++) f[i]=1;
    for(int i=1;i<=n;i++){
        int x=s[i];
        if(f[x]!=-1&&f[fa[x]]!=-1){
            if(f[x]+f[fa[x]]>=k){
                ans++;
                f[fa[x]]=-1;
            }
            else f[fa[x]]=max(f[fa[x]],f[x]+1);
        }
    }
    return ans;
}
int main()
{
    n=read();
    for(i=1;i<n;i++){
        int u=read(),v=read();
        insert(u,v);
        insert(v,u);
    }
    dfs(1,0);
    int m=sqrt(n*log2(n)),l,r,mid,tmp;
    ans[1]=n;
    for(i=2;i<=m;i++) ans[i]=dp(i);
    for(i=m+1;i<=n;i=l+1){
        l=i,r=n;
        tmp=dp(i);
        while(l<r){
            mid=(l+r+1)/2;
            if(dp(mid)==tmp) l=mid;
            else r=mid-1;
        }
        for(j=i;j<=l;j++) ans[j]=tmp;
    }
    for(i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/LSlzf/p/11878448.html