快乐游戏鸡

题目描述

题解

令f[x][w]表示从x出发,当前已经死了w次,要再死一次的最少步数。

然而开不下。。。

或者令g[x][w]表示从x出发,走w步会在死一次时,最多会死多少次。

然而好像也开不下。。。

不过我们发现,答案是一个阶梯型分段函数。

因为其中隐藏着单调性,死的越多,那么下一次死的时候走的路程肯定是不降的。

差不多长这样。。。

所以我们可以维护这个分段函数,然后对于询问,我们把询问挂在s上,此时t在s的子树里,我们就可以处理询问了。

但是怎么维护呢?

前置知识:长链剖分

下面说的其实和这题没半毛钱关系。

长链剖分相比于重链剖分,每个点的重儿子指的不是size最大的儿子,而是maxdeep最大的子树。

长链剖分可以做这样一个性质:O(1)查找某个点的k极祖先。

怎么找呢?我们先长链剖分,然后对于每一条长链开一个vector,先令这条链长度为len,里面存的是这条链上面的len极祖先,这个是O(n)的。

然后再把这棵树倍增一下。

对于一个询问,我们先跳k的第一个二进制位,这个是O(1)的。然后我们跳的距离一定>k/2,然后有一个性质,就是跳上去的那个点所在的脸长一定>k/2。

这个比较显然,然后k极祖先所在点就在那条链里了。

那么长链剖分在这道题里有什么用呢。

观察到这个分段函数我们可以用队列存下来它的每一个拐点,然后每次从子树向父亲转移时用队列合并一下。

因为我们的队列是以deep为下标的,所以队列长度是不超过maxdeep的,所以我们可以用长链剖分来均摊复杂度。

这里还是用了DSU on tree的思想,先把长链加入队列,再把其他子树加进来,这样貌似是nlogn?

对于队列,因为是一颗树,所以我们可以采用dfs序分配内存,这样我们只需要开一个O(n)的队列了。

算贡献极麻烦。。。、

代码

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<vector>
#define N 300009
using namespace std;
typedef long long ll;
int n,fa[N],tot,head[N],p[N][20],ma[N][20],len[N],son[N],h[N],t[N],dfnn,top,dfn[N];
ll w[N],sum[N],deep[N],ans[N];
inline ll rd(){
    ll x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
struct edge{int n,to;}e[N];
struct node{ll dep,val;}q[N],st[N];
vector<node>vec[N];
inline void add(int u,int v){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;}
void dfs(int u){
    for(int i=1;(1<<i)<=deep[u];++i)p[u][i]=p[p[u][i-1]][i-1],ma[u][i]=max(ma[u][i-1],ma[p[u][i-1]][i-1]);
    for(int i=head[u];i;i=e[i].n){
        int v=e[i].to;p[v][0]=u;ma[v][0]=w[u];
        deep[v]=deep[u]+1;
        dfs(v);
        if(len[v]>len[son[u]])son[u]=v; 
    }
    len[u]=len[son[u]]+1;
}
int getlca(int s,int t){
    ll ans=0;
    for(int i=19;i>=0;--i)if(deep[s]-(1<<i)>deep[t]){
        ans=max(ans,(ll)ma[s][i]);s=p[s][i];
    }
    return ans;
} 
inline void push(int u,node y){
    while(h[u]<=t[u]&&y.val>=q[h[u]].val)h[u]++;
    if(h[u]<=t[u]&&y.dep==q[h[u]].dep)return;
    q[--h[u]]=y;
    if(h[u]!=t[u])sum[h[u]]=q[h[u]+1].dep*(q[h[u]+1].val-q[h[u]].val)+sum[h[u]+1];
    else sum[h[u]]=0;
}
inline void merge(int x,int y){
    top=0;
    while(h[x]<=t[x]&&q[h[x]].dep<=q[t[y]].dep)st[++top]=q[h[x]++];
    while(top&&h[y]<=t[y])
      if(st[top].dep>q[t[y]].dep)push(x,st[top--]);
      else push(x,q[t[y]--]);
    while(top)push(x,st[top--]);
    while(h[y]<=t[y])push(x,q[t[y]--]);
}
void solve(int u,int tt,int id){
    ll val=getlca(tt,u);
    int l=h[u],r=t[u],as=l;
    while(l<=r){
        int mid=(l+r)>>1;
        if(val<=q[mid].val){
            as=mid;r=mid-1;
        }else l=mid+1;
    }
    int tag=val>=q[h[u]].val;
    if(tag)ans[id]=sum[h[u]]-sum[as]+q[h[u]].dep*q[h[u]].val+(val-q[as].val)*q[as].dep;
    else ans[id]=val*q[h[u]].dep;
    ans[id]+=deep[tt]-deep[u]-deep[u]*val;
}
void ddfs(int u){
    dfn[u]=++dfnn;
    if(son[u])ddfs(son[u]),h[u]=h[son[u]],t[u]=t[son[u]];
    else h[u]=dfnn+1,t[u]=dfnn;
    for(int i=head[u];i;i=e[i].n){
        int v=e[i].to;if(v==son[u])continue;
        ddfs(v);
        merge(u,v);
    }
    for(int i=0;i<vec[u].size();++i){
        int v=vec[u][i].dep,id=vec[u][i].val;
        solve(u,v,id);
    }    
    push(u,node{deep[u],w[u]});
}
int main(){
    n=rd();
    for(int i=1;i<=n;++i)w[i]=rd();
    for(int i=2;i<=n;++i){
        fa[i]=rd();add(fa[i],i);
    }
    dfs(1);
    int qu=rd();int ss,tt;
    for(int i=1;i<=qu;++i){
        ss=rd();tt=rd();    
        vec[ss].push_back(node{tt,i});
    }
    ddfs(1);
    for(int i=1;i<=qu;++i)printf("%lld\n",ans[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/ZH-comld/p/10296951.html