[Luogu P3571] [BZOJ 3835] [POI2014]SUP-Supercomputer

洛谷传送门
BZOJ传送门

题目描述

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

输入输出格式

输入格式

第一行两个正整数 N , Q N, Q N,Q

第二行 Q Q Q个正整数, 分别表示每组询问中的 K K K

第三行 N − 1 N-1 N1个正整数, 第 i i i个正整数表示 i + 1 i+1 i+1号节点的父节点。

输出格式

输出一行 Q Q Q个正整数, 表示对于每组询问最小的操作次数。

输入输出样例

输入样例#1:
20 1
3
1 1 1 3 4 3 2 8 6 9 10 12 12 13 14 11 11 11 11
输出样例#1:
8

解题分析

实在太菜了, 想不到 d p dp dp方程…

这个方程是这样的:

d p [ i ] dp[i] dp[i]表示每次最多选 i i i个节点, s u m [ i ] sum[i] sum[i]表示深度 ≥ i \ge i i的节点总数, 就有:
d p [ i ] = m a x ( j + ⌈ s u m [ j + 1 ] i ⌉ ) dp[i]=max(j+\lceil\frac{sum[j+1]}{i}\rceil) dp[i]=max(j+isum[j+1])
大概意思就是前 j j j次取完前 j j j层, 后面每次都保证取满 i i i个节点。

为什么要取 m a x max max?因为有不合法的情况。

  • j j j次取不完, 那么就有 k &lt; j k&lt;j k<j满足前 k k k次是取完的, 容易证明此时不会比我们假设的不合法的 j j j次取完 j ​ j​ j层更优。

  • 后面取不满, 那么就有 k &gt; j ​ k&gt;j​ k>j满足后面是取完的, 同样可得此时不会比这种不合法的情况更优。

然后如果有一个 k &gt; j k&gt;j k>j满足
j + ⌈ s u m [ j + 1 ] i ⌉ &lt; k + ⌈ s u m [ k + 1 ] i ⌉ j+\lceil\frac{sum[j+1]}{i}\rceil&lt;k+\lceil\frac{sum[k+1]}{i}\rceil j+isum[j+1]<k+isum[k+1]
就有
i j + s u m [ j + 1 ] &lt; i k + s u m [ k + 1 ] ij+sum[j+1]&lt;ik+sum[k+1] ij+sum[j+1]<ik+sum[k+1]
移项可得
i ( k − j ) &gt; s u m [ j + 1 ] − s u m [ k + 1 ] i &gt; s u m [ j + 1 ] − s u m [ k + 1 ] k − j i(k-j)&gt;sum[j+1]-sum[k+1] \\ i&gt;\frac{sum[j+1]-sum[k+1]}{k-j} i(kj)>sum[j+1]sum[k+1]i>kjsum[j+1]sum[k+1]
很简单的斜率优化。

注意这里所有的 j , k j,k j,k的取值范围都是 [ 1 , m a x d e p ] [1,maxdep] [1,maxdep], 所以先插入完, 最后挨个查询即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <cstdlib>
#include <vector>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define db double
#define MX 1000050
#define ll long long
template <class T>
IN void in(T &x)
{
	x = 0; R char c = gc;
	for (; !isdigit(c); c = gc);
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
}
int n, q, head = 0, tail = -1, dpmx, qmx;
std::vector <int> nex[MX];
int s[MX], dp[MX], que[MX], dep[MX], ask[MX];
IN db slope(R int ini, R int tar) {return 1.0 * (s[ini + 1] - s[tar + 1]) / (tar - ini);}
IN void insert(R int id)
{
	W (head < tail && slope(que[tail - 1], que[tail]) >= slope(que[tail], id)) --tail;
	que[++tail] = id;
}
void DFS(R int now)
{
	dpmx = std::max(dpmx, dep[now]);
	++s[dep[now]];
	for (auto i : nex[now])
	{
		dep[i] = dep[now] + 1;
		DFS(i);
	}
}
int main(void)
{
	in(n), in(q); int foo;
	for (R int i = 1; i <= q; ++i)
	in(ask[i]), qmx = std::max(qmx, ask[i]);
	for (R int i = 2; i <= n; ++i)
	in(foo), nex[foo].push_back(i);
	dep[1] = 1; DFS(1);
	for (R int i = dpmx - 1; ~i; --i) s[i] += s[i + 1];
	for (R int i = 0; i < dpmx; ++i) insert(i);
	for (R int i = 1; i <= qmx; ++i)
	{
		W (head < tail && slope(que[head], que[head + 1]) < i) ++head;
		dp[i] = que[head] + (s[que[head] + 1] + i - 1) / i;
	}
	for (R int i = 1; i <= q; ++i) printf("%d ", dp[ask[i]]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值