Codeforces 893F Subtree Minimum Query(Hard) 主席树

19 篇文章 0 订阅
14 篇文章 0 订阅

题目大意:给定一棵有根树,点x有点权a[x],多组询问,每次询问以x为根的子树中的所有满足dep[y]-dep[xi]<=ki的y中,最小的a[y]。n<=1e5, q<=1e6。强制在线。
题解:按照dfs序重新编号,这个题等价于求编号在[L[x],R[x]],深度在[dep[x],dep[x]+k]中的点权最小值。
因此就是一个二维数点(权最值)。但是随意一个矩形的最值类询问还要树套树什么的(因为没有办法做减法),但是注意到编号在[L[x],R[x]]深度在[0,dep[x])的点爷不存在,所以询问等价于编号在[L[x],R[x]],深度在[1,dep[x]+k]的点有多少,这样就可以按照深度为第一关键字建主席树即可。
代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define INF INT_MAX
#define N 200010
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
struct edges{
    int to,pre;
}e[N<<1];
int in[N],out[N],id[N],dep[N],dfs_clock,h[N],etop;
inline int add_edge(int u,int v)
{
    return e[++etop].to=v,e[etop].pre=h[u],h[u]=etop;
}
inline bool depcmp(int a,int b)
{
    return dep[a]<dep[b];
}
int dfs(int x,int fa)
{
    in[x]=++dfs_clock,dep[x]=dep[fa]+1;
    for(int i=h[x];i;i=e[i].pre)
        if(e[i].to^fa) dfs(e[i].to,x);
    return out[x]=dfs_clock;
}
struct segment{
    int l,r,v;
    segment *ch[2];
}*T[N];
int build(segment* &rt,int l,int r)
{
    rt=new segment;rt->l=l,rt->r=r,rt->v=INF;
    if(l==r) return 0;int mid=(l+r)>>1;
    return build(rt->ch[0],l,mid),build(rt->ch[1],mid+1,r);
}
inline int push_up(segment* &rt)
{
    return rt->v=min(rt->ch[0]->v,rt->ch[1]->v);
}
int update(segment* &now,segment* &pre,int p,int v)
{
    now=new segment;int l=now->l=pre->l,r=now->r=pre->r;
    int mid=(l+r)>>1,g=p>mid;if(l==r) return now->v=v;
    update(now->ch[g],pre->ch[g],p,v),now->ch[g^1]=pre->ch[g^1];
    return push_up(now);
}
int query(segment* &rt,int s,int t)
{
    int l=rt->l,r=rt->r,mid=(l+r)>>1;
    if(s<=l&&r<=t) return rt->v;int ans=INF;
    if(s<=mid) ans=min(ans,query(rt->ch[0],s,t));
    if(mid<t) ans=min(ans,query(rt->ch[1],s,t));
    return ans;
}
int a[N],t[N];
int main()
{
//  freopen("data.in","r",stdin);
//  freopen("std.out","w",stdout);
    int n,r;scanf("%d%d",&n,&r),build(T[0],1,n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1,u,v;i<n;i++)
        scanf("%d%d",&u,&v),add_edge(u,v),add_edge(v,u);
    for(int i=1;i<=n;i++) id[i]=i;
    dfs(r,0),sort(id+1,id+n+1,depcmp);
//  for(int i=1;i<=n;i++) debug(i)sp,debug(in[i])sp,debug(out[i])sp,debug(dep[i])ln;
//  for(int i=1;i<=n;i++) debug(id[i])sp;cerr ln;
    for(int i=1;i<=n;i++)
        update(T[i],T[i-1],in[id[i]],a[id[i]]);
//      debug(i)sp,debug(id[i])sp,debug(a[id[i]])ln;
    for(int i=1,j=1;i<=n;t[dep[id[i]]]=j,j=++i)
        while(j<n&&dep[id[i]]==dep[id[j+1]]) j++;
    int maxd=0;for(int i=1;i<=n;i++) maxd=max(maxd,dep[i]);
//  for(int i=1;i<=maxd;i++) debug(i)sp,debug(t[i])ln;
    int m,las=0,p,q,x,k;scanf("%d",&m);
    while(m--)
        scanf("%d%d",&p,&q),x=(las+p)%n+1,k=(las+q)%n,
        printf("%d\n",las=query(T[t[min(maxd,dep[x]+k)]],in[x],out[x]));
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]中提到了一种树形动态规划的方法来解决CodeForces - 982C问题。在这个问题中,subtree指的是子连通块,而不是子树。为了使cnt_white - cnt_black尽可能大,可以使用两次树形动态规划来求解。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。如果ans\[u\]为正,则减去dp\[v\]就是树链所代表的子树的权值。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] 问题: CodeForces - 982C 树形DP是什么问题?如何解决? 回答: CodeForces - 982C是一个树形动态规划问题。在这个问题中,需要求解子连通块的最大权值和,使得cnt_white - cnt_black尽可能大。解决这个问题的方法是使用两次树形动态规划。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] #### 引用[.reference_title] - *1* *2* [CodeForces - 1324F Maximum White Subtree(树形dp)](https://blog.csdn.net/qq_45458915/article/details/104831678)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值