题目大意:

给你一颗n个节点的完全二叉树,从根节点标号为1。标号为x的节点的左、右儿子标号分别为:2x、2x+1。这棵树的每个节点的权值为它本身的标号。现在告诉你有m次操作,每次操作要么就是把一个点变成给定值,要么就是让你输出经过给定某点的一条最长路径的长度。(一条路径的长度就是它经过的每个点的权值和,包括端点)
数据范围:$n<1e8,m<1e5$

分析:

首先,显然对于这种树上的问题,直觉上就是要划分子树或者一层一层的遍历才是比较快的。

dp的建立:

设:dp[ i ]表示标号为i的节点向下最长能走多长的路径。
那么对于每一次的查询,就是从这个节点开始,一直向上遍历,枚举它的每个祖先作为根节点的时候得到的最长路径,时间:O( log n ) 。每一次更新就是更新该节点,并向上遍历更新他的每个祖先,时间:O( log n )。

关于hash优化:

但是发现,如果要储存每个 dp 的值,那就需要 1e8 的空间,这显然是存不开的。但是想到,在更改之前,对于每个点的查询,我是可以以O( log n )的时间求出他的对应的 dp 值的,详细求法在下文说。然后假如我只记录我需要修改的点,那么最多只需要记录 m*log n 个点就足够了,这个是可以用 hash 表来记录的。

下面来说明对于一次查询操作,得到一个点 dp[ i ] 的具体操作过程:

如果该点并没有被存到hash表中就说明以这个点为根节点的子树的所有节点都没有被更改过,那么我就只要比较它的左右儿子的子节点层数,如果层数相等,我就加上它的右子节点,如果层数不等,就加上它的左子节点。之后继续向下判断。如果该点被hash记录过了,那么就是这个值,直接返回就好。

代码:

#include<bits/stdc++.h>
using namespace std;

int n;int q;
char s[20];
map<int,long long int>f;
map<int,long long int>a;

int deep(int x)
{
    int ans=0;
    while(x<=n)
    {
        ans++;
        x*=2;
    }
    return ans;
}
long long int get_v(int x)//得到以x号结点开始向下能走到的最大值
{
    if(x>n)return 0;
    if(f.count(x)>0)return f[x];
    long long int ans=(a.count(x)?a[x]:x);
    if(deep(x)==1)return ans;
    if(deep(x*2+1)==deep(x*2))
    {
        ans+=get_v(x*2+1);
    }
    else
    {
        ans+=get_v(x*2);
    }
    return ans;
}
void change()
{
    int x;long long int v;
    scanf("%d%lld",&x,&v);//标号x结点值变为v
    a[x]=v;
    long long int t=max(get_v(x*2+1),get_v(x*2))+v;
    f[x]=t;
    while(x>1)
    {
        x/=2;
        f[x]=max(get_v(x*2),get_v(x*2+1))+(a.count(x)?a[x]:x);
    }
}
void query()
{
    int x;
    scanf("%d",&x);
    if(x==1)
    {
        printf("%lld\n",get_v(x*2)+get_v(x*2+1)+(a.count(x)?a[x]:x));
        return ;
    }
    long long int k=get_v(x);
    long long int best=get_v(x*2)+get_v(x*2+1)+(a.count(x)?a[x]:x);
    while(x>1)
    {
        int other;
        if(x%2==0)other=x+1;
        else other=x-1;
        x/=2;
        k+=(a.count(x)?a[x]:x);
        long long int temp=k+get_v(other);
        if(temp>best)best=temp;
    }
    printf("%lld\n",best);
}


int main()
{
    while(scanf("%d%d",&n,&q)!=EOF)
    {
        a.clear();f.clear();
        while(q--)
        {
            scanf("%s",s);
            if(s[0]=='q')query();
            if(s[0]=='c')change();
        }
    }
    return 0;
}

另附测试数据生成代码(输入n m即可随机输出若干组测试数据):

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int n,m;
    srand( (unsigned)time( NULL ) );
    //freopen()
    scanf("%d%d",&n,&m);
    while(m--)
    {
        if(rand()%2==1)
        {
            printf("query %d\n",(rand()%n)+1);
        }
        else
        {
            printf("change %d %d\n",(rand()%n)+1,(rand()%n)+1);
        }
    }
}