给你一个树,边的连接方式是i与i/2连一条无向边,长度为l[i]。q个询问,每个询问给出一个点a和一个值H,求以a为起点到任意点为终点获得(happiness=H-路径长度)不为负数的所有happiness之和。
看了题解才弄懂的,每一个点递增有序地储存以它为根节点,所有子节点(包括自己)到它的距离并处理出前缀和。所需总空间为O(nlog(n)),然后对于每个询问,从询问点不断往上爬(编号/2就是了)并且减去边长,在每一层时,二分查询左右子树lch和rch(不是刚爬上来的那个子树)中满足条件(总路径小于H)的最大编号,然后就是直接利用前缀和贡献答案就是了。时间复杂度为O(nlog(n) + m(log(n))2)——使用归并排序的情况下(预处理时从下往上处理就可以使用归并了)。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn=1000010;
vector<ll> e[maxn],sum[maxn];
ll n,m,len[maxn],a,h;
ll ans;
ll query(ll x,ll h)
{
if(h<=0)
return 0;
ll p = upper_bound(e[x].begin(),e[x].end(),h)-e[x].begin();
return p*h-sum[x][p-1];
}
void init()
{
for(int i=n;i>=1;i--)
{
e[i].push_back(0);
int lc=i*2, rc=i*2+1;
if(lc<=n)
for(int j=0;j<e[lc].size();j++)
e[i].push_back(e[lc][j]+len[lc]);
if(rc<=n)
for(int j=0;j<e[rc].size();j++)
e[i].push_back(e[rc][j]+len[rc]);
sort(e[i].begin(),e[i].end());
sum[i].resize(e[i].size());
for(int j=1;j<e[i].size();j++)
sum[i][j]=sum[i][j-1]+e[i][j];
}
}
int main() {
cin>>n>>m;
for(int i=2;i<=n;i++)
scanf("%lld",&len[i]);
init();
while(m--)
{
ans=0;
scanf("%lld%lld",&a,&h);
ll last=0;
while(a && h>0)
{
ans+=h;
ll lc=2*a,rc=2*a+1;
if(lc!=last && lc<=n)
ans+=query(lc,h-len[lc]);
if(rc!=last && rc<=n)
ans+=query(rc,h-len[rc]);
last=a;
h-=len[a];
a/=2;
}
printf("%lld\n",ans);
}
return 0;
}