【BZOJ】3835: [Poi2014]Supercomputer

题意

\(n(1 \le 1000000)\)个点的有根树,\(1\)号点为根,\(q(1 \le 1000000)\)次询问,每次给一个\(k\),每一次可以选择\(k\)个未访问的点,且父亲是访问过的,要求最少次数访问完所有的点。

分析

神题不会做。

题解

得到一个式子\(ans=max(i+ \left \lceil \frac{s[i]}{k} \right \rceil), 0 \le i \le maxh\),其中\(maxh\)是最大深度,\(s[i]\)是深度大于\(i\)的点的数量。证明如下:
定义关键层\(i\)表示拿了\(i\)次后、前\(i\)层已经拿完,以后每一次都可以拿\(k\)个(最后一次除外)。我们需要证明:1、存在关键层。2、关键层的解是最小解。
首先我们按照下面两个规则查询:

  1. 如果当前层\(i\)的结点不够\(k\),则查询完,而且如果之前有\(j < i\)层的点没查询,则查询完。
  2. 如果足够了\(k\),则在保证最终能在第\(maxh\)次遍历到第\(maxh\)层的情况下,随便选\(k\)个。

那么显然在最后一个执行1操作而且那层\(i\)不存在\(j < i\)层的点没查询的\(i\)就是关键层。由于这样的1操作至少有一个(即第1层肯定是执行的是这样的1操作),所以关键层肯定存在,而且只有一个。
至于关键层的解是否是最小解,感觉很显然,然而不会严格证明。
至于取max,不会严格证明。

最后原题可以转化为\(ans=\left \lceil max(i+\frac{s[i]}{k}) \right \rceil\),所以按照\(i+\frac{s[i]}{k}\)来斜率优化就行辣。

#include <bits/stdc++.h>
using namespace std;
inline int getint() {
    int x=0;
    char c=getchar();
    for(; c<'0'||c>'9'; c=getchar());
    for(; c>='0'&&c<='9'; x=x*10+c-'0', c=getchar());
    return x;
}
typedef long long ll;
const int N=1000005;
int a[N], d[N], q[N], c[N], ihead[N], cnt;
struct E {
    int next, to;
}e[N];
void add(int x, int y) {
    e[++cnt]=(E){ihead[x], y}; ihead[x]=cnt;
}
void dfs(int x) {
    for(int i=ihead[x]; i; i=e[i].next) {
        d[e[i].to]=d[x]+1;
        dfs(e[i].to);
    }
}
inline bool ok1(int i, int j, int k) {
    return (ll)(c[j]-c[i])*(k-i)>(ll)(c[k]-c[i])*(j-i);
}
inline bool ok2(int b, int j, int k) {
    return (ll)b*(j-k)>c[k]-c[j];
}
int main() {
    int n=getint(), Q=getint();
    for(int i=0; i<Q; ++i) {
        a[i]=getint();
    }
    d[0]=1;
    int mx=1;
    for(int i=1; i<n; ++i) {
        add(getint()-1, i);
    }
    dfs(0);
    for(int i=0; i<n; ++i) {
        ++c[d[i]-1];
        mx=max(mx, d[i]);
    }
    for(int i=mx; i; --i) {
        c[i]+=c[i+1];
    }
    int *fr=q+1, *ta=q;
    for(int i=1; i<=mx; ++i) {
        for(; fr<ta && ok1(*(ta-1), i, *ta); --ta);
        *++ta=i;
    }
    for(int i=1; i<=n; ++i) {
        for(; fr<ta && ok2(i, *(fr+1), *fr); ++fr);
        d[i]=*fr+(c[*fr]+i-1)/i;
    }
    for(int i=0; i<Q; ++i) {
        printf("%d%c", a[i]>n?mx:d[a[i]], " \n"[i==Q-1]);
    }
    return 0;
}

转载于:https://www.cnblogs.com/iwtwiioi/p/4986048.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值