树形DP + 优化
题意:
有n个节点标号1~n,根据其公式建树,每个节点有权值最开始就是标号本身,有两种操作,分别是:
- 修改u节点的值为v
- 问经过u节点的路径中最大的权值和是多少
思路:
对于经过一个节点的路径的权值和来说最大的情况有两种:1. 经过这个节点的父亲加上经过这个节点的某一个儿子的路径 2. 经过这个节点的两个儿子
不妨设dp[i] 表示经过节点的儿子路径的权值和的最大值。
那么每次询问的时候只需找出上面的两种情况比较既可,第二种情况很容易找出,第一种情况需要回溯或者说一个循环即可,因为根据二叉树的性质当前结点除以2就是父亲节点,经过父亲节点的最大值也有两种情况:1 经过了父亲的父亲 2. 经过了父亲的另一个儿子。
所以对于当前结点u来说,需要依次枚举父亲节点,然后判断最大值既可。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;
typedef long long LL;
int n,m;
map<int,LL>dp,val;
LL cal(int u)
{
if(u > n) return 0;
if(dp.count(u)) return dp[u];
int t = u,ls = 0,rs = 0;
while((t<<1) <= n) {
ls++;
t = t<<1;
}
t = u;
while((t<<1|1) <= n) {
rs++;
t = t<<1|1;
}
if(rs != ls) t = n;
LL ans = 0;
while(t >= u) {
ans += (val.count(t)?val[t]:t);
t >>= 1;
}
return ans;
}
void update(int u,int v)
{
val[u] = v;
while(u) {
dp[u] = max(cal(u<<1),cal(u<<1|1)) + (val.count(u)?val[u]:u);
u >>= 1;
}
}
LL query(int u)
{
LL ans = cal(u<<1) + cal(u<<1|1) + (val.count(u)?val[u]:u);
LL FaDist = cal(u);
while( (u>>1) > 0) {
int flag = (u%2)?1:0;
u >>= 1;
FaDist += val.count(u) ? val[u]:u;
if(flag) ans = max(ans,FaDist + cal(u<<1));
else ans = max(ans,FaDist + cal(u<<1|1));
}
return ans;
}
int main()
{
//freopen("in.txt","r",stdin);
while(scanf("%d%d",&n,&m) != EOF) {
dp.clear();val.clear();
for(int i = 1;i <= m; i++) {
int u,x;
char s[10];
scanf("%s%d",s,&u);
if(s[0] == 'q')
printf("%I64d\n",query(u));
else {
scanf("%d",&x);
update(u,x);
}
}
}
return 0;
}