HDU - 5840 - This world need more Zhu(树链剖分)

HDU - 5840 - This world need more Zhu

将询问分为 k 大和 k 小的两部分。假设 kB 的部分为 k 小。
对于 k 大的情况,我们考虑直接暴力。把每一条边分成向上和向下的两条边:
对于向上的边,开始点 (d[x]d[u]+1)%k==0 。故 d[x]=d[u]k+1
对于向下的边,开始点 (d[x]d[lca]+1+d[u]d[lca])%k==0 d[x]=d[lca]+(d[lca]d[u]1)%k
可以直接从开始点向上/向下走,每次询问复杂度为 O(nB)
对于 k 小的情况,我们考虑使用树剖。
对于一条链 (u,v)
对于向上的边 x 被计数当且仅当 (d[u]d[x]+1)%k==0 。故 d[x]=(d[u]+1)%k
对于向下的边 x 被计数当且仅当 (d[x]d[lca]+d[u]d[lca]+1)%k==0 。故 d[x]=(2×d[lca]d[u]1)%k
对于每一个 k 。可以考虑将树上所有点根据第一关键字 d[i]%k 升序,第二关键字树剖 dfs 序升序来排序,放到线段树上。这样对于一个询问,我们只需要访问一条链上模 k 值给定的一段就行。对于每条重链,要访问的一段应该是连续的,因为重链的 dfs 序连续。一次询问复杂度是 O(log2n)
因此总的复杂度是 O(QlargenB+n+Bn+Qsmalllog2n)
B 100 。这样总复杂度可以到 108 左右。

#include<bits/stdc++.h>
#define lson (rt<<1)
#define rson (rt<<1|1)
using namespace std;
const int N=1e5+7;
const int BLOCK=100;
int n,a[N],ans[N],son[N],fa[N],top[N],sz[N],id[N],d[N],b[N],cnt,st[N],tp,mx[N<<2],L[BLOCK],R[BLOCK],tmp[N];
vector<int> hs[BLOCK],adj[N];
struct Query
{
    int u,v,k,down,lca,id;
};
vector<Query> q1[N],q2[BLOCK];
inline int LCA(int u,int v)
{
    while(top[u]!=top[v])
    {
        if(d[top[u]]<d[top[v]]) swap(u,v);
        u=top[u];
        u=fa[u];
    }
    return d[u]<d[v]?u:v;
}
inline void init()
{
    memset(a,0,sizeof(a));
    memset(ans,0,sizeof(ans));
    top[1]=1;
    tp=cnt=0;
    memset(son,-1,sizeof(son));
    for(int i=1;i<=n;++i) q1[i].clear(),adj[i].clear();
    for(int i=0;i<BLOCK;++i) q2[i].clear();
}
void dfs1(int u,int deep,int father)
{
    sz[u]=1;d[u]=deep;fa[u]=father;
    for(int v : adj[u])
    {
        if(v==father) continue;
        dfs1(v,deep+1,u);
        sz[u]+=sz[v];
        if(son[u]==-1||sz[v]>sz[son[u]])
            son[u]=v;
    }
}
void dfs2(int u)
{
    id[u]=++cnt;
    b[cnt]=u;
    if(son[u]!=-1)
    {
        top[son[u]]=top[u];
        dfs2(son[u]);
    }
    for(int v : adj[u])
    {
        if(v==fa[u]||v==son[u]) continue;
        top[v]=v;
        dfs2(v);
    }
}
void work_1(int u)
{
    st[tp++]=u;
    for(Query & q : q1[u])
    {
        int u=q.u,v=q.v,lca=q.lca,k=q.k,id=q.id,down=q.down;
        if(down)
        {
            int tmp=d[lca]+((d[lca]-d[u]-1)%k+k)%k;
            while(tmp<tp&&d[st[tmp]]<=d[v]) ans[id]=max(ans[id],a[st[tmp]]),tmp+=k;
        }
        else
        {
            int tmp=tp-k;
            while(tmp>=0&&d[st[tmp]]>=d[lca]) ans[id]=max(ans[id],a[st[tmp]]),tmp-=k;
        }
    }
    for(int v : adj[u])
    {
        if(v==fa[u]) continue;
        work_1(v);
    }
    --tp;
}

inline void push_up(int rt)
{
    mx[rt]=max(mx[lson],mx[rson]);
}
void build(int rt,int l,int r)
{
    if(l==r)  { mx[rt]=a[b[tmp[l]]];return ; }
    int m=(l+r)>>1;
    build(lson,l,m);
    build(rson,m+1,r);
    push_up(rt);
}
int query(int rt,int l,int r,int ql,int qr)
{
    if(ql<=l&&qr>=r) return mx[rt];
    int m=(l+r)>>1;
    int res=0;
    if(ql<=m) res=max(res,query(lson,l,m,ql,qr));
    if(qr>m) res=max(res,query(rson,m+1,r,ql,qr));
    return res;
}
inline int cal(int l,int r,int k)
{
    l=lower_bound(tmp+L[k],tmp+R[k]+1,l)-tmp;
    r=upper_bound(tmp+L[k],tmp+R[k]+1,r)-tmp-1;
    return l<=r?query(1,1,n,l,r):0;
}
inline void solve(int x,int u,int k,int id)
{
    if(d[u]<d[x]) return ;
    while(top[u]!=top[x])
    {
        ans[id]=max(ans[id],cal(::id[top[u]],::id[u],k));
        u=fa[top[u]];
    }
    ans[id]=max(ans[id],cal(::id[x],::id[u],k));
}
inline void work_2(int k)
{
    for(int i=0;i<k;++i) hs[i].clear();
    for(int i=1;i<=n;++i) hs[d[b[i]]%k].push_back(i);
    int tot=0;
    for(int i=0;i<k;++i)
    {
        L[i]=tot+1;
        for(int v : hs[i]) tmp[++tot]=v;
        R[i]=tot;
    }
    build(1,1,n);
    for(Query & q : q2[k])
    {
        int u=q.u,v=q.v,k=q.k,lca=q.lca,id=q.id;
        solve(lca,u,(d[u]+1)%k,id);
        solve(lca,v,((2*d[lca]-d[u]-1)%k+k)%k,id);
    }
}
int main()
{
    int T,m;
    scanf("%d",&T);
    for(int kase=1;kase<=T;++kase)
    {
        scanf("%d%d",&n,&m);
        init();
        for(int i=1;i<=n;++i) scanf("%d",&a[i]);
        for(int i=1;i<n;++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            adj[u].push_back(v);
            adj[v].push_back(u);
        }
        dfs1(1,0,1);
        dfs2(1);
        for(int i=0;i<m;++i)
        {
            int u,v,k;
            scanf("%d%d%d",&u,&v,&k);
            if(k<BLOCK) q2[k].push_back({u,v,k,1,LCA(u,v),i});
            else
            {
                q1[u].push_back({u,v,k,0,LCA(u,v),i});
                q1[v].push_back({u,v,k,1,LCA(u,v),i});
            }
        }
        work_1(1);
        for(int i=0;i<BLOCK;++i) if(q2[i].size()) work_2(i);
        printf("Case #%d:\n",kase);
        for(int i=0;i<m;++i)
            printf("%d\n",ans[i]);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值