题目链接:894D
题目意思:
给你一个树,边的连接方式是i与i/2连一条无向边,长度为l[i]。q个询问,每个询问给出一个点a和一个值H,求以a为起点到任意点为终点获得(happiness=H-路径长度)不为负数的所有happiness之和。
可以发现这个题目中所给出的树是一棵拍立非常整齐的二叉树(emmmmmm。。。类似于线段树的那种树形结构)
某个节点k的左儿子是2*k,右儿子是2*k+1。现在要求某个结点到其他任意结点的距离小于所给的H的H-len的和。由于询问是1e5次所以查询只能是logn之类。
看了题解才弄懂的,每一个点递增有序地储存以它为根节点,所有子节点(包括自己)到它的距离并处理出前缀和。所需总空间为O(nlog(n)),然后对于每个询问,二分一下查找这个结点中符合条件的加上(前缀和已经处理出来了)。同时这里要注意,在询问点不断往上爬的过程中在深度较低的结点不能加上已经爬过了的结点下符合条件的数(因为已经在前面加过了。。)。由于我不想再开两个vector,(可能会爆),所以我在深度较低的结点处理时,我先加上所有符合条件的点的数(<val-point),再在上一个结点找所有能够符合条件的数减去(<val-point-len[pre])注意,这里要再减去len[pre]。
#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 1e6+9;
vector<long long > vec[MAX_N];
vector<long long > sum[MAX_N];
long long len[MAX_N];
void init()
{
for(int i=0;i<MAX_N;i++)
{
vec[i].clear();
sum[i].clear();
vec[i].push_back(0);
}
}
int main()
{
int N,M,T;
while(cin>>N>>M)
{
init();
for(int i=2;i<=N;i++)
{
scanf("%lld",&len[i]);
}
// 预处理前缀和
for(int i=N;i>=1;i--)
{
int t = i;
long long int s = len[t];
while(t/2)
{
int temp = t %2;
t /= 2;
vec[t].push_back(s);
s += len[t];
}
sort(vec[i].begin(),vec[i].end());
long long temp = 0;
for(int j=0;j<vec[i].size();j++)
{
temp += vec[i][j];
sum[i].push_back(temp);
}
}
for(int i=0;i<M;i++)
{
long long ans = 0;
long long point = 0;
long long pre = 0;
long long x,val;
scanf("%lld%lld",&x,&val);
while(x)
{
long long int pos = upper_bound(vec[x].begin(),vec[x].end(),val-point)-vec[x].begin();
pos --;
if(pos>=0)
{
ans += (val-point)*(pos+1) - sum[x][pos];
}
if(pre != 0)
{
pos = upper_bound(vec[pre].begin(),vec[pre].end(),val-point-len[pre]) - vec[pre].begin(); // 注意len[pre]不等于point
pos--;
if(pos>=0)
{
ans -= (pos+1)*(val-point) - (sum[pre][pos] + (pos+1)*len[pre]); //这个公式比较难推
}
}
point += len[x];
if(val-point < 0) break;
pre = x;
x /= 2;
}
printf("%lld\n",ans);
}
}
return 0;
}