给你一个树,边的连接方式是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;
}
本文介绍了一种结合树形动态规划与二分搜索的算法,用于解决一类特定问题:在给定的树结构中,寻找从指定起点出发到任意终点的路径,使得路径长度不超过给定值H时,所有可能路径的幸福值之和。文章详细阐述了算法的实现过程,包括如何预处理树的每个节点以存储其子节点距离的递增有序列表,并通过二分搜索快速查询满足条件的路径。
723

被折叠的 条评论
为什么被折叠?



