题目1
题意:
给定一棵带权二叉树,共有m次访问。对于一次访问x,h,输出以x为起点,任意点y为终点,dis(x,y)小于h,贡献为h-dis(x,y)。
1
≤
n
≤
1
0
6
,
1
≤
m
≤
1
0
5
,
1
≤
A
i
≤
n
,
0
≤
H
i
≤
1
0
7
1 ≤ n ≤ 10^6, 1 ≤ m ≤ 10^5,1 ≤ A_i ≤ n, 0 ≤ H_i ≤ 10^7
1 ≤ n ≤ 106,1 ≤ m ≤ 105,1 ≤ Ai ≤ n,0 ≤ Hi ≤ 107
分析:
根据题目我们很容易得出这是一棵满二叉树,二叉树的性质就是本题的关键点了。由于是满二叉树,所以每个节点可以维护其所有子孙节点到他的距离。对于一次访问的x和h,先处理x的子树满足条件的,满足条件的用upper_bound找,在维护一个前缀和就可以logn算出答案的贡献,然后再往父节点转移,父节点做的就是去剩下的那棵子树计算满足条件的点,再转移到他的父节点,由于满二叉树,最多logn层。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
struct node{
int id;
ll val;
node(int a,ll b)
{
id = a;
val = b;
}
};
vector<node> g[1000005];
vector<ll> dis[1000005],sum[1000005];
ll v[1000005];
void dfs(int x)
{
if( g[x].size() == 0 )
{
dis[x].push_back(0);
sum[x].push_back(0);
return;
}
for (int i = 0; i < g[x].size(); i++)
{
int t = g[x][i].id;
dfs(t);
for (int j = 0; j < dis[t].size(); j++)
{
dis[x].push_back(dis[t][j]+g[x][i].val);
}
}
dis[x].push_back(0);
sort(dis[x].begin(),dis[x].end());
ll sumx = 0;
for (int i = 0; i < dis[x].size(); i++)
{
sumx += dis[x][i];
sum[x].push_back(sumx);
}
}
ll cal(int x,int h)
{
ll ans = 0;
int t = upper_bound(dis[x].begin(),dis[x].end(),h) - dis[x].begin();
if( t != 0 ) ans += (ll)h * t - sum[x][t-1];
return ans;
}
int n;
void slove(int x,int h)
{
ll ans = 0;
ans += cal(x,h);
int temp = x;
while( temp != 1 )
{
h -= v[temp-1];
if( h <= 0 ) break;
ans += h;
x = temp;
temp /= 2;
int t1 = temp * 2;
int t2 = temp * 2 + 1;
if( t2 > n ) continue;
if( t1 == x )
{
ans += cal(t2,h-v[t2-1]);
}else ans += cal(t1,h-v[t1-1]);
}
cout << ans << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int m;
cin >> n >> m;
for (int i = 1; i < n; i++)
{
cin >> v[i];
g[(i+1)/2].push_back(node(i+1,v[i]));
}
dfs(1);
for (int i = 1; i <= m; i++)
{
int x,h;
cin >> x >> h;
slove(x,h);
}
return 0;
}