题目
给定一棵N个节点的有根树,根节点为1。Q次询问,每次给定一个K,用最少的操作次数遍历完整棵树,输出最少操作次数。每次操作可以选择访问不超过K个未访问的点,且这些点的父亲必须在之前被访问过。
思路
首先选点遵循两个原则:
- 每次操作都尽可能的选取更多的节点
- 每次操作都尽可能的选取还有儿子的节点
也就是说每次操作都优先选择还有儿子的节点,如果能选择的有儿子的节点数目小于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;
}