ZOJ 3937 More Health Points (2016 浙江省赛 B题,可持久维护凸壳)

题目链接  2016 ZJCPC Problem B

题意  CF 660F的树上版本。

其他做的方法都差不多,关键是把凸壳放到树上。

每次确定扔掉几个元素的时候直接$O(1)$修改(先不清楚这个位置之后的元素因为之后还要恢复),然后$O(1)$恢复,通过这个来实现可持久。

#include <bits/stdc++.h>

using namespace std;

#define rep(i, a, b)	for (int i(a); i <= (b); ++i)
#define dec(i, a, b)	for (int i(a); i >= (b); --i)
#define MP		make_pair
#define fi		first
#define se		second

typedef long long LL;

const int N = 1e5 + 10;

int T;
int n;
int r;
int deep[N], q[N];
LL a[N], c[N], s[N];
LL ans;
vector <int> v[N];

inline LL y(int x){
	return c[x] - 1ll * deep[x] * s[x];
}

inline long double g(int j, int k){
	double dy = 1.00 * y(j) - 1.00 * y(k);
	double dx = 1.00 *  deep[j] - 1.00 * deep[k];
	return dy / dx;
}

inline LL calc(int x, int y){
	return c[x] - c[y] - 1ll * deep[y] * (s[x] - s[y]);
}


void pre(int x, int fa, int dep){
	deep[x] = dep;
	s[x] = s[fa] + a[x];
	c[x] = c[fa] + 1ll * dep * a[x];

	for (auto u : v[x]){
		pre(u, x, dep + 1);
	}
}

inline int pos(LL x, int tail){
	x = -x;
	int l = 1, r = tail - 1, ret = 0;

	while (l <= r){
		int mid = (l + r) >> 1;
		if (g(q[mid], q[mid - 1]) < x) l = (ret = mid) + 1;
		else r = mid - 1;
	}

	return q[ret];
}

inline int gettail(int x, int tail){
	int l = 2, r = tail, ret = 0;

	if (g(x, q[1]) < g(q[1], q[0])) return 1;

	while (l <= r){
		int mid = (l + r) >> 1;
		if (g(x, q[mid - 1]) >= g(q[mid - 1], q[mid - 2])) l = (ret = mid) + 1;
		else r = mid - 1;
	}

	return ret;
}

void dfs(int x, int tail){

	int y = pos(s[x], tail);
	ans = max(ans, calc(x, y));

	int cnt, t, re, la;
	if (tail <= 1 || g(x, q[tail - 1]) >= g(q[tail - 1], q[tail - 2])){
		re = tail;
		la = q[tail];
		q[tail] = x;
		tail++;
		cnt = tail;
	}	

	else{
		t = gettail(x, tail);    //get the position
		re = t;
		la = q[t];
		q[t] = x;
		cnt = t + 1;
	}
	//replace

	for (auto u : v[x]) dfs(u, cnt);  //continue solving

	q[re] = la;  //undo
}		

int main(){

	scanf("%d", &T);
	while (T--){
		scanf("%d", &n);
		rep(i, 0, n + 1) v[i].clear();

		rep(i, 1, n) scanf("%lld", a + i);
		rep(i, 2, n){
			int x;
			scanf("%d", &x);
			v[x].push_back(i);
		}

		pre(1, 0, 1);

		rep(i, 0, n + 1) q[i] = 0;
		r = 0;
		q[r++] = 0;

		ans = 0;
		dfs(1, r);
		printf("%lld\n", ans);
	}


	return 0;
}

  

转载于:https://www.cnblogs.com/cxhscst2/p/8961729.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值