HDU 6161 Big binary tree(树形DP+map)

256 篇文章 0 订阅
58 篇文章 1 订阅

Description

给出一个 n 个节点的完全二叉树,1节点为根节点, x 节点的父亲节点为x2 x 节点的权值为x,定义一条路径的权值为该条路径所经过节点的权值之和, m 种操作,操作有两种

change u x: u 节点的权值改为x

query u: 查询所有经过 u 节点路径权值的最大值

Input

多组用例,每组用例首先输入两个整数n,m表示点数和操作数,之后 m 行每行一个操作(1n108,1m105,1x1010)

Output

对于每个查询,输出结果

Sample Input

6 13
query 1
query 2
query 3
query 4
query 5
query 6
change 6 1
query 1
query 2
query 3
query 4
query 5
query 6

Sample Output

17
17
17
16
17
17
12
12
12
11
12
12

Solution

先考虑查询操作,令 sum(u) 为从叶子节点到 u 节点路径权值最大值,val(u) u 节点权值,那么一条经过u节点的权值最大的路径必然由两条从叶子节点到 v 节点的路径组成,其中某条路径要经过u v 节点为u的祖先节点,那么可以首先求出 sum(u) ,然后从 u 开始往树根走去枚举每个v,这个过程中我们可以得到从叶子节点到 u 节点再到v节点的某个儿子节点(假设是左儿子 ls(v) )的路径权值最大值,另一条路径显然要选择从叶子节点到 rs(v) 的路径权值最大值,即 sum(rs(v)) ,再加上 val(v) 即为当前 v 对应的最优解

然后考虑更新操作对sum,val的影响,如果没有对 u 节点做过修改则val(u)=u,否则对 val(u) 做对应的修改即可,而对于 sum(u) ,如果以 u 为根的子树的节点权值均没有被修改过,由于点的编号就是其权值,如果以u为根的子树是满二叉树,那么从最优叶子节点走到 u 最优,但是如果以u为根的子树不是满二叉树,则要从 n 节点走到u更优,判断以 u 为根的子树是否为满二叉树可以从u开始分别尽量往左走或尽量往右走,如果两边可以走的节点数相同则为满二叉树,否则不是,但如果修改了 u 节点的权值,对所有的sum(v)均可能产生影响,其中 v 节点为u节点的祖先节点, 只需要从 u 开始往根节点走把这条路径上所有点v sum(v) 做修改即可,即 sum(v)=max(sum(ls(v)),sum(rs(v)))+val(v)

注意到这里 n 很大,对sum,val均开数组不现实,但是由于被修改的 sum,val 值不多,每次修改至多影响 log2n 个点,这样最多被影响的点只有 mlog2n 个,故用两个 map 存被修改了的 sum,val ,对于未受影响的点的 sum 值直接 O(log2n) 暴力求即可,总时间复杂度 O(mlog22n)

Code

#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
map<int,ll>sum,val; 
int n,q;
int ls(int t)
{
    return (t<<1);
}
int rs(int t)
{
    return ((t<<1)|1);
}
int get_l(int t)
{
    int num=0;
    while(ls(t)<=n)t=ls(t),num++;
    return num;
}
int get_r(int t,int &m)
{
    int num=0;
    while(rs(t)<=n)t=rs(t),num++;
    m=t;
    return num;
}
ll deal(int u)
{
    if(u>n)return 0;
    if(sum.count(u))return sum[u];
    int m;
    int L=get_l(u),R=get_r(u,m);
    if(L!=R)m=n;
    ll ans=0;
    while(m!=u)
    {
        ans+=m;
        m>>=1;
    }
    return ans+u;
}
void update(int t,ll x)
{
    val[t]=x;
    while(t)
    {
        sum[t]=max(deal(ls(t)),deal(rs(t)))+(val.count(t)?val[t]:t);
        t>>=1;
    }
}
ll query(int t)
{
    ll ans=deal(ls(t))+deal(rs(t))+(val.count(t)?val[t]:t);
    ll res=deal(t);
    int flag=t%2;
    t>>=1;
    while(t)
    {
        res+=(val.count(t)?val[t]:t);
        if(flag)ans=max(ans,res+deal(ls(t)));
        else ans=max(ans,res+deal(rs(t)));
        flag=t%2;
        t>>=1;
    }
    return ans;
}
int main()
{
    while(~scanf("%d%d",&n,&q))
    {
        sum.clear(),val.clear();
        char op[11];
        int u;
        ll x;
        while(q--)
        {
            scanf("%s%d",op,&u);
            if(op[0]=='q')printf("%I64d\n",query(u));
            else 
            {
                scanf("%I64d",&x);
                update(u,x); 
            } 
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值