题目

给定一棵N个节点的有根树,根节点为1。Q次询问,每次给定一个K,用最少的操作次数遍历完整棵树,输出最少操作次数。每次操作可以选择访问不超过K个未访问的点,且这些点的父亲必须在之前被访问过。

思路

首先选点遵循两个原则:

  1. 每次操作都尽可能的选取更多的节点
  2. 每次操作都尽可能的选取还有儿子的节点

也就是说每次操作都优先选择还有儿子的节点,如果能选择的有儿子的节点数目小于k,就继续选择没有儿子的节点,直到没有节点可以选或者k次选择用完。

这样贪心的做绝对能够保证答案是正确的。

接下来观察一下数据范围,所有数据都在1e6以内,数据输入也是先输入询问,再输入图,这摆明了需要让我们在O(n)的时间离线预处理,然后O(1)的时间查询。

再看看题目,要求输出最小的操作次数,最小。很有DP的特征,并且DP可以做到预先处理出答案实现O(1)查询。那么就考虑一下DP吧!
列出方程后可以斜率优化

代码

#include<bits/stdc++.h>
#define calc(i,k) i*k+s[i]
using namespace std;
const int N=1e6+77;
struct E
{
    int to,nx;
    E(){}
    E(int to,int nx):to(to),nx(nx){}
}e[N];
int cnt,ls[N],maxk,s[N],f[N],t1,t2,q[N],a[N],b[N],n,m,v[N],maxdep,deep[N];
void build(int a,int b)
{
    e[++cnt]=E(b,ls[a]);ls[a]=cnt;
}
void chkmax(int &a,int b) {a=a<b?b:a;}
void dfs(int u,int dep)
{
    chkmax(maxdep,dep);deep[dep]++;
    for(int i=ls[u];i;i=e[i].nx)
    dfs(e[i].to,dep+1);
}
bool slope(int p1, int p2, int p3)
{
    return (b[p3]-b[p1])*(a[p2]-a[p1])-(b[p2]-b[p1])*(a[p3]-a[p1])>=0;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&v[i]);
        chkmax(maxk,v[i]);
    }
    for(int i=2,x;i<=n;i++)
    {
        scanf("%d",&x);
        build(x,i);
    }
    dfs(1,1);
    for(int i=maxdep;i>=1;i--)
    s[i]=s[i+1]+deep[i+1];
    for(int i=1;i<=maxdep;i++) a[i]=i,b[i]=s[i];
    for(int i=1;i<=maxdep;i++)
    {
        while(t1<t2&&slope(q[t2-1],q[t2],i)) t2--;
        q[++t2]=i;
    }
    for(int i=1;i<=maxk;i++)
    {
        while(t1<t2&&calc(q[t1],i)<=calc(q[t1+1],i)) t1++;
        f[i]=q[t1]+(s[q[t1]]+i-1)/i;
    }
    for(int i=1;i<=m;i++) printf("%d ",f[v[i]]);
    return 0;
}