BZOJ3835: [Poi2014]Supercomputer

111 篇文章 0 订阅
1 篇文章 0 订阅

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

设s[i]表示深度大于i的点的个数

首先,我们需要知道,最优方案一定是先用i步拿完了前i层的点,然后再每次取k个,直到取完

证明如下:

首先我们先一层一层的拿,当这层的点大于k个时,我们挑选其中任意k个,当这层点的个数少于k个时,我们就可以拿前几层没拿的点,这样直到某一层的时候,满足了前面的点都被拿光,而之后有方案可以保证每次都能拿满k个,这一层就是最后的i

所以对于每一个k,相当于求 max(i+s[i]k),1imaxdep
化简之后就变成了 max(ki+s[i])k,1imaxdep
也就是说对于每一个k,只需要求出 max(ki+s[i]) 就好了
我们可以把ix+s[i]当做一个一次函数,然后对于不同的i形成的这些一次函数维护一个凸壳,然后把k排好序在上面扫一遍就可以求出最大值了

#include<iostream>
#include<cstdio>
#define N 1000010
using namespace std;
int a[N];
int to[N],nxt[N],pre[N],cnt;
void ae(int ff,int tt)
{
    cnt++;
    to[cnt]=tt;
    nxt[cnt]=pre[ff];
    pre[ff]=cnt;
}
int d[N],fa[N],M;
int c[N];
void build(int x)
{
    int i,j;
    d[x]=d[fa[x]]+1;
    M=max(M,d[x]);
    c[d[x]]++;
    for(i=pre[x];i;i=nxt[i])
    {
        j=to[i];
        build(j);
    }
}
int ans[N];
int s[N],t;
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    int i,j,x,y;
    for(i=1;i<=m;i++)
    scanf("%d",&a[i]);
    for(i=2;i<=n;i++)
    {
        scanf("%d",&fa[i]);
        ae(fa[i],i);
    }
    build(1);
    for(i=M-1;i>=1;i--)
    c[i]+=c[i+1];
    s[1]=1;s[2]=2;t=2;
    for(i=3;i<=M;i++)
    {
        while(t>1&&(s[t]-s[t-1])*(c[i]-c[s[t-1]])-(c[s[t]]-c[s[t-1]])*(i-s[t-1])>=0) t--;
        t++;s[t]=i;
    }
    int tmp=0;
    cout<<endl;
    for(i=1;i<=t;i++)
    cout<<s[i]<<' '<<c[s[i]]<<endl;*/
    j=1;
    for(i=1;i<=n;i++)
    {
        while(j<t&&i*s[j]+c[s[j]]<i*s[j+1]+c[s[j+1]]) j++;
        ans[i]=s[j];
    }
    for(i=1;i<=m;i++)
    {
        x=a[i];
        if(x>=n) printf("%d",M);
        else printf("%d",max(ans[x]-1+(c[ans[x]]+x-1)/x,M));
        if(i!=m) putchar(' ');
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值