[BZOJ3835][POI2014]Supercomputer

bzoj
luogu

description

有一棵\(n\)个节点的树,有\(q\)次询问,每次给出一个\(k\),你需要用最少的操作次数选中树上的每一个点。每一次操作可以选中树上的至多\(k\)个点,但是要保证这些点的父亲都已经被选了(根一开始就可以选)。
\(n,q\le10^6\)

sol

考虑一下选择的过程。一开始瓶颈在于深度,一次操作可能选不满\(k\)个。当达到某个阈值时,瓶颈变为\(k\),即每次都可以选满\(k\)个点。
我们可以枚举那个阈值\(i\),设\(s_i\)表示深度大于\(i\)的点的个数,那么\(ans=\max\{i+\lceil\frac{s_i}{k}\rceil\}\)。(所有的\(i\)中有且仅有一个\(i\)作为阈值是合法的,而不合法的情况都会把答案算小,所以就取\(\max\)啦。)
然后把\(i\)也放到上面去,得到\(ans=\max\{\lceil\frac{ik+s_i}{k}\rceil\}\)。实际上就是要最大化\(ik+s_i\)
\(k\)视作自变量,那么我们就只需要维护直线\(y=ix+s_i\)构成的上凸壳就行了。
复杂度\(O(n\log n+q\log n)\)
upt:突然意识到一个严重的问题,建凸壳为什么要带\(\log\)???我一定是傻逼。
但是我这带\(\log\)跑得比\(O(n)\)的快是什么鬼。

code

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
#define ll long long
const int N = 1e6+5;
int n,m,d,k[N],dep[N],s[N],top;
struct node{int p,l,r;}q[N];
ll cal(int p,int x){
    return 1ll*p*x+s[p];
}
int binary(int x,int y){
    int l=q[top].l,r=n,res=0;
    while (l<=r){
        int mid=l+r>>1;
        if (cal(x,mid)<cal(y,mid)) res=mid,r=mid-1;
        else l=mid+1;
    }
    return res;
}
int main(){
    n=gi();m=gi();
    for (int i=1;i<=m;++i) k[i]=gi();
    for (int i=2;i<=n;++i) ++s[dep[i]=dep[gi()]+1],d=max(d,dep[i]+1);
    for (int i=d;i;--i) s[i]+=s[i+1];
    for (int i=1;i<=d;++i){
        while (top&&cal(q[top].p,q[top].l)<cal(i,q[top].l)) --top;
        if (!top) q[++top]=(node){i,1,n};
        else{
            int x=binary(q[top].p,i);
            q[top].r=x-1;q[++top]=(node){i,x,n};
        }

    }
    for (int i=1;i<=m;++i){
        int l=1,r=top,res=0;
        while (l<=r){
            int mid=l+r>>1;
            if (q[mid].l<=k[i]) res=mid,l=mid+1;
            else r=mid-1;
        }
        printf("%lld ",(cal(q[res].p,k[i])+k[i]-1)/k[i]);
    }
    puts("");return 0;
}

转载于:https://www.cnblogs.com/zhoushuyu/p/9267403.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值