BZOJ3835: [Poi2014]Supercomputer

138 篇文章 0 订阅
77 篇文章 0 订阅

不会做看题解qwq,感觉这个结论…不知道怎么想啊qwq
用sum[i]表示层数>=i的点数,那么有 ans=maxi=1 to maxdep(i+sum[i+1]k)
这个柿子的意思是:用i次操作搞定前i层,接下来每次操作都能取满k个或把剩余不足k个取完

为什么答案就是这个呢…

首先答案不会比这个优了,因为每次操作最多搞定一层,所以i次操作搞定前i层是上限了,而剩下的每次操作又拿满,所以答案不会比这个更优

那么为什么不会比这个差呢
(我不严谨的证明一下,意会一下?
首先我们让dis[i]表示i子树最大深度,每次操作拿点的策略肯定是选dis[i]最大的,即能往下拓展最远的那些优先拓展(因为他们比较有前景qwq),其他的如果能取的点还有多的再考虑他们

那么先证i次操作能拿完前i层
假设我们用到的是值相同的,最小的i
假设i次拿不完前i层
那么一定是出现了这样的情况
这里写图片描述
(中间的节点懒得画了
(哇我画的是真的丑= =
即我们用j次操作拿完了前j层(j可以是1),然后第j+1到i层点太多了,i次操作取不完
那么一定有 sum[j]sum[i+1]k>=sum[i+1]k+ij
那么我们一定会取j而不是取i贡献答案,所以i次操作一定能拿完前i层

再证剩下每次操作都可以取满
如果我们剩下的操作有的取不满,导致了实际答案更大,一定是有类似这样的情况
这里写图片描述
就是用i次操作拿完了前i层,然后第i+1到k层的操作都顺利拿满了,然而k层到j层中间的节点很少,因为每次最多往下拓展一层,导致k层到j层的操作拿不满导致了答案增大,那么显然这时候i不能使ans取得max
所以也不会有这种情况,即剩下每次操作都能拿满

于是证明了这个柿子 ans=maxi=1 to maxdep(i+sum[i+1]k)
剩下的工作就简单了,画一下柿子
ans=max(sum[i+1]+ikk))
就是随着k变大,维护 sum[i+1]+ik 最大值
维护一个斜率单调的队列就行了

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

inline void read(int &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
const int maxn = 1100000;
const double eps = 1e-9;

int n,Q;
int dep[maxn];
struct edge{int y,nex;}a[maxn<<1]; int len,fir[maxn];
inline void ins(const int x,const int y){a[++len]=(edge){y,fir[x]};fir[x]=len;}
void dfs(const int x,const int d)
{
    dep[d]++;
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) dfs(a[k].y,d+1);
}

struct node{int x,y;}p[maxn],q[maxn]; int head,tail,pn;
inline ll multi(node x,node y,node z)
{
    x.x-=z.x; x.y-=z.y;
    y.x-=z.x; y.y-=z.y;
    ll re=(ll)x.x*y.y-(ll)x.y*y.x;
    if(re>0) re=1ll;
    return re<0?-1:re;
}
inline bool cmp(node x,node y,int k) { return (double)(y.y-x.y)-(double)k*(y.x-x.x)>=eps; }

int ask[maxn],ans[maxn];

int main()
{
    read(n); read(Q);
    for(int i=1;i<=Q;i++) read(ask[i]);
    for(int i=2;i<=n;i++) 
    {
        int x; read(x);
        ins(x,i);
    }
    dfs(1,1);
    for(int i=n;i>=1;i--) dep[i]+=dep[i+1];
    for(int i=1;i<=n&&dep[i];i++) p[++pn]=(node){i,-dep[i+1]};

    head=1,tail=0;
    for(int i=1;i<=pn;i++)
    {
        while(head<tail&&multi(q[tail],p[i],q[tail-1])<=0) tail--;
        q[++tail]=p[i];
    }
    for(int k=1;k<maxn;k++)
    {
        while(head<tail&&!cmp(q[head],q[head+1],k)) head++;
        ans[k]=q[head].x+(-q[head].y+k-1)/k;
    }
    for(int i=1;i<Q;i++) printf("%d ",ans[ask[i]]);
    printf("%d\n",ans[ask[Q]]);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值