codeforces 932D. Tree(树上倍增)

题意:

给出一个初始点只有1的树,权值为0,有两种操作:

1. 1. 1. 往树中加入一个节点

2.查询从某个节点起,往祖先方向能找到的最长非降子序列。要求:权值和不能超过 x x x ,并且后一个点是前一个点的祖先,且必须是最近的,权值 ≥ \geq 前一个点的祖先。

操作强制在线。

题解:

像这种往祖先方向查询的操作,可以考虑倍增法。

d p [ i ] [ j ] dp[i][j] dp[i][j]​ 表示节点 i i i 向上找长度为 2 j 2^j 2j 的非降子序列后所处的位置

s u m [ i ] [ j ] sum[i][j] sum[i][j] 表示节点 i i i 向上找长度为 2 j 2^j 2j 的非降子序列后的和。

那么只需找到 d p [ i ] [ 0 ] dp[i][0] dp[i][0]即可接连求出 d p [ i ] [ j ] dp[i][j] dp[i][j]

要想找到第一个大于等于 i i i 的祖先,就要根据节点 i i i 的父亲 f a fa fa 来求得。

1.如果 f a fa fa的权值大于等于 i i i ,那么 d p [ i ] [ 0 ] = f a dp[i][0]=fa dp[i][0]=fa

2. 2. 2.​ 否则,找到 一个最大的 j j j​满足 w [ d p [ f a ] [ j ] ] < w [ i ] w[dp[fa][j]]<w[i] w[dp[fa][j]]<w[i]​ ,然后跳到 d p [ f a ] [ j ] dp[fa][j] dp[fa][j]​ 继续重复操作。为什么可以直接跳呢?

假设中间存在一个大于等于 w [ i ] w[i] w[i]的位置,那么不论这个数有没有被选到序列中, w [ d p [ f a ] [ j ] ] w[dp[fa][j]] w[dp[fa][j]]都一定会大于等于这个数,那么自然也就大于等于 w [ i ] w[i] w[i] ,所以如果 w [ d p [ f a ] [ j ] ] < w [ i ] w[dp[fa][j]]<w[i] w[dp[fa][j]]<w[i],那么就可以直接跳。

代码:

#pragma GCC diagnostic error "-std=c++11"
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<ctime>
#define iss ios::sync_with_stdio(false)
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int,int> pii;
const int mod=1e9+7;
const int MAXN=5e5+5;
const ll inf=0x3f3f3f3f3f3f3f3f;
int last=0;
int cnt=1;
ll w[MAXN],dp[MAXN][30],sum[MAXN][30];
void add(ll x,ll y)
{
    int u=(x^last);
    w[++cnt]=(y^last);
    //cout<<u<<endl;
    if(w[cnt]<=w[u]) dp[cnt][0]=u;
    else{
        for(int i=20;i>=0;i--){
            if(w[cnt]>w[dp[u][i]]){
                u=dp[u][i];
            }
        }
        dp[cnt][0]=dp[u][0];
    }
    if(dp[cnt][0]==0) sum[cnt][0]=inf;
    else sum[cnt][0]=w[dp[cnt][0]];
    for(int i=1;i<=20;i++){
        dp[cnt][i]=dp[dp[cnt][i-1]][i-1];
        if(dp[cnt][i]==0) sum[cnt][i]=inf;
        else sum[cnt][i]=sum[dp[cnt][i-1]][i-1]+sum[cnt][i-1];
    } 

}
int query(ll x,ll y)
{
    int r=(x^last);
    ll cost=(y^last)-w[r];
    int ans=0;
    if(cost>=0) ans++;
    for(int i=20;i>=0;i--){
        if(sum[r][i]<=cost){
            //cout<<sum[r][i]<<" "<<cost<<endl;
            ans+=(1<<i);
            cost-=sum[r][i];
            r=dp[r][i];
        }
    }
    return ans;
}
int main()
{
    int n;
    scanf("%d",&n);
    w[0]=inf;
    w[1]=0; 
    for(int i=0;i<=20;i++){
        sum[1][i]=inf;
    }
    while(n--){
        ll op,x,y;
        scanf("%lld%lld%lld",&op,&x,&y);
        if(op==1){
            add(x,y);
        }
        else{
            last=query(x,y);
            printf("%d\n",last);
        }
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值