【LG-P3920/WC2014】 紫荆花之恋

9 篇文章 0 订阅

Solution

在此提供一种可能相对其他解法较为好写的做法。

首先考虑一棵如下图的树,其中 r t l r 0 = 6 ,   r t l r 1 = 5 rtlr_0=6,\ rtlr_1=5 rtlr0=6, rtlr1=5

1

发现此时这棵树不考虑根节点只有两条链,而左右链的末尾分别是 r t l r 0 rtlr_0 rtlr0 r t l r 1 rtlr_1 rtlr1

同时,按照套路,我们将 d i s t ( i , j ) ≤ r i + r j dist(i,j) \leq r_i+r_j dist(i,j)ri+rj 化为 d i s t ( i , l ) − r i ≤ r j − d i s t ( j , l ) dist(i, l)-r_i\leq r_j-dist(j,l) dist(i,l)rirjdist(j,l),其中 l l l i i i j j j 的最近公共祖先。

对于上图,我们只用维护 4 棵平衡树(使用平衡树因为我们是在维护在某一集合内比某数要大的值的个数)就可以把答案求出,具体实现如下:


inline void add(int u, int v, int w){
	e[++cnt] = (edge){v, hd[u], w}, hd[u] = cnt;
	e[++cnt] = (edge){u, hd[v], w}, hd[v] = cnt;
}

inline void rotate(int &x, int k){
	int y = t[x].ch[k];
	t[x].ch[k] = t[y].ch[!k], t[y].ch[!k] = x;
	t[y].s = t[x].s, 
	t[x].s = t[t[x].ch[1]].s + t[t[x].ch[0]].s + 1;
	x = y;
}

inline void insrt(int &x, int v){
	if(!x){
		x = ++tot;
		t[x].s = 1, t[x].a = v;
		return;
	}
	bool k = (v >= t[x].a);
	insrt(t[x].ch[k], v), t[x].s += 1;
	if(t[x].s * 0.8 < t[t[x].ch[k]].s) 
		rotate(x, k);
}

inline int query(int x, int v){
	if(!x) return 0;
	if(v <= t[x].a) 
		return query(t[x].ch[0], v) + t[t[x].ch[1]].s + 1;
	else return query(t[x].ch[1], v);
}


int main(){
	rd(), n = rd(), rd(), rd(), val[1] = rd();
	rtlr[0] = rtlr[1] = 1;
	printf("0\n");
	insrt(rt1[0], val[1]), insrt(rt1[1], val[1]);//rt1/2[0]维护左边的分支 
	insrt(rt2[0], val[1]), insrt(rt2[1], val[1]);//rt1/2[1]维护右边的分支 
	for(i = 2; i <= n; ++i){
		f[i] = rd() ^ ans % mod, j = rd(), val[i] = rd();
		if(f[i] != rtlr[1] and f[i] != rtlr[0]) break;//如果此时不满足只有两条链的形态,就 break 掉 
		add(i, f[i], j);
		dis[i] = dis[f[i]] + j;//dis[i] 为 i 到根节点的距离 
		fx = (f[i] == rtlr[1])/*看 i 在左右哪条链上*/, rtlr[fx] = i;
		ans += query(rt1[fx], dis[i] - val[i]) + query(rt2[!fx], dis[i] - val[i]); 
		if(dis[i] <= val[1] + val[i]) ans -= 1;//减去重复计算的 
		insrt(rt1[fx], dis[i] + val[i]), insrt(rt2[fx], val[i] - dis[i]);//rt1和rt2分别插入不同的值,辅助求解答案 
		printf("%lld\n", ans); 
	} 
}

然后我们再来考虑当树的形态不满足上面的要求时,如下图:

2

1. 求解答案

面对这种情况,我们对于每个点要维护两棵平衡树:

一棵用于记录以当前节点 i i i 为根的树内每个子节点 j j j r j − d i s t ( j , f a i ) r_j-dist(j, fa_i) rjdist(j,fai) 的值,这棵平衡树的根的编号记为 m s i ms_i msi

一棵用于记录当前节点 i i i,它其中一个儿子 j j j j j j 子树内的所有子节点 k k k r k − d i s t ( k , i ) r_k-dist(k,i) rkdist(k,i) 的值,记为 g s i gs_i gsi(注意 g s i gs_i gsi 中并没有记录 i i i 的直接儿子 j j j 的信息)。

对于每个节点,我们再去维护它分别到每一个深度的祖先的距离,对于第 k k k 层的祖先,节点 i i i 到它的距离记为 a d i s k , i adis_{k,i} adisk,i

然后我们试着去求一下对于新增的一个节点 i i i,最终答案是多少。

过程如下:

	for(k = dep[f[i]], l = f[i], o = i; l; k--, o = l, l = f[l])
		u = adis[k][i] - val[i],
		ans += query(gs[l], u) - query(ms[o], u);

我们从下往上去遍历每一个祖先,对于每一个祖先,我们利用刚刚对每个节点维护的两棵平衡树进行求解答案。

2. 添加新节点

然后我们就考虑如何把这个节点加入这棵树内。

首先,肯定是按照刚刚求答案那样,从下往上去遍历祖先,对于每一个祖先,我们分别在它的两棵平衡树内插入新节点的信息即可。

但是如果单单只是这样,树的深度就可能会无止境地增加,这绝不是我们想要的。

所以这时候就要请出替罪羊树了! 我们用替罪羊树的思路,在从下往上遍历祖先的同时,去检查每棵子树的大小有没有超过其父亲子树大小的 0.88 倍。

如果有,就把它记录下来,然后就对它进行重构。

重构一棵树的步骤:

  1. “拍扁”这棵树,然后选出一个大小刚刚好超过或等于总节点数一半的节点,把它当做新根。

  2. 对于这个新根,我们去建立它的新平衡树,然后对它的每一个儿子,重复第 1 和 2 步。

然后这道题就结束了,复杂度可过。

虽然口胡起来并不复杂,但是代码实现的时候还是需要注意很多细节的。

Code

#include<bits/stdc++.h>
using namespace std;

#define ll long long
const int maxn = 1e5 + 5;
const int mod = 1000000000;

inline int rd(){
	int x = 1, s = 0;
	char ch = getchar();
	while(ch < '0' or ch > '9'){
		if(ch == '-') x = -1;
		ch = getchar();
	}
	while(ch >= '0' and ch <= '9')
		s = s * 10 + ch - '0', ch = getchar();
	return s * x;
}

struct tree{
	int s, a;
	int ch[2];
}t[maxn * 200];
bool fx;
int rt1[2], rt2[2], rtlr[2];
int n, m, i, j, k, l, o, u, v;
int val[maxn], f[maxn];
int cnt, hd[maxn];
struct edge{
	int to, nxt, w;
}e[maxn << 1];
int tsiz[maxn], siz[maxn], in[maxn], id[maxn];
int gs[maxn], ms[maxn];
int dep[maxn], dis[maxn], adis[100][maxn];
int pre[maxn], he, ta, que[maxn];
int tot;
ll ans;

inline void add(int u, int v, int w){
	e[++cnt] = (edge){v, hd[u], w}, hd[u] = cnt;
	e[++cnt] = (edge){u, hd[v], w}, hd[v] = cnt;
}

inline void rotate(int &x, int k){
	int y = t[x].ch[k];
	t[x].ch[k] = t[y].ch[!k], t[y].ch[!k] = x;
	t[y].s = t[x].s, 
	t[x].s = t[t[x].ch[1]].s + t[t[x].ch[0]].s + 1;
	x = y;
}

inline void insrt(int &x, int v){
	if(!x){
		x = ++tot;
		t[x].s = 1, t[x].a = v;
		return;
	}
	bool k = (v >= t[x].a);
	insrt(t[x].ch[k], v), t[x].s += 1;
	if(t[x].s * 0.8 < t[t[x].ch[k]].s) 
		rotate(x, k);
}

inline int query(int x, int v){
	if(!x) return 0;
	if(v <= t[x].a) 
		return query(t[x].ch[0], v) + t[t[x].ch[1]].s + 1;
	else return query(t[x].ch[1], v);
}

inline int fndrt(int x, int tdep, int tdis, int fa){
	int i, j, k;
	he = ta = 0;
	que[ta++] = x, dis[x] = tdis;
	pre[x] = 0;
	while(he != ta){
		i = que[he++];
		dep[i] = tdep + 1, tsiz[i] = 1;
		for(j = hd[i]; j; j = e[j].nxt){
			k = e[j].to;
			if(k != pre[i] and dep[k] >= tdep)
				que[ta++] = k, pre[k] = i, 
				dis[k] = dis[i] + e[j].w;
		}
		if(tdep) adis[tdep - 1][i] = dis[i];
	}
	for(i = ta - 1; i >= 0; --i){
		k = pre[que[i]];
		tsiz[k] += tsiz[que[i]];
		if(tsiz[que[i]] * 2 >= ta) break;
	}
	j = que[i], f[j] = fa, siz[j] = ta, dep[j] = tdep;
	if(tdep) in[j] = x, id[j] = tdis;
	else in[j] = id[j] = 0;
	return j;
}

inline void build(int x){
	int i, j, k, l, o;
	adis[dep[x]][x] = gs[x] = 0;
	insrt(gs[x], val[x]);
	for(i = hd[x], j = e[i].to; i; i = e[i].nxt, j = e[i].to){
		if(dep[j] < dep[x]) continue;
		k = fndrt(j, dep[x] + 1, e[i].w, x);
		ms[k] = 0;
		for(l = 0; l < siz[k]; ++l){
			o = que[l];
			insrt(gs[x], val[o] - dis[o]);
			insrt(ms[k], val[o] - dis[o]);
		}
		build(k);
	}
}

inline void calc(int i){
	add(i, f[i], j);
	dep[i] = dep[f[i]] + 1, siz[i] = 1, 
	in[i] = i, id[i] = j;
	insrt(gs[i], val[i]);
	for(k = 0; k < dep[i]; ++k) 
		adis[k][i] = adis[k][f[i]] + j;
	for(k = dep[f[i]], l = f[i], o = i; l; k--, o = l, l = f[l])
		u = adis[k][i] - val[i],
		ans += query(gs[l], u) - query(ms[o], u);
	printf("%lld\n", ans);
	for(k = dep[f[i]], v = 0, l = f[i], o = i; l; k--, o = l, l = f[l]){
		u = val[i] - adis[k][i];
		insrt(gs[l], u), insrt(ms[o], u);
		siz[l] += 1;
		if(siz[l] * 0.88 < siz[o]) v = l;
	}
	if(v){
		if(dep[v]) u = fndrt(in[v], dep[v], id[v], f[v]);
		else u = fndrt(1, 0, 0, 0);
		ms[u] = ms[v], ms[v] = 0;
		build(u); 
	}
}

int main(){
	rd(), n = rd(), rd(), rd(), val[1] = rd();
	rtlr[0] = rtlr[1] = 1;
	printf("0\n");
	insrt(rt1[0], val[1]), insrt(rt1[1], val[1]);
	insrt(rt2[0], val[1]), insrt(rt2[1], val[1]);
	for(i = 2; i <= n; ++i){
		f[i] = rd() ^ ans % mod, j = rd(), val[i] = rd();
		if(f[i] != rtlr[1] and f[i] != rtlr[0]) break;
		add(i, f[i], j);
		dis[i] = dis[f[i]] + j;
		fx = (f[i] == rtlr[1]), rtlr[fx] = i;
		ans += query(rt1[fx], dis[i] - val[i]) + query(rt2[!fx], dis[i] - val[i]); 
		if(dis[i] <= val[1] + val[i]) ans -= 1;
		insrt(rt1[fx], dis[i] + val[i]), insrt(rt2[fx], val[i] - dis[i]);
		printf("%lld\n", ans); 
	} 
	if(i > n) return 0;
	for(k = 1; k <= tot; ++k) 	
		t[k].ch[0] = t[k].ch[1] = 0;
	tot = 0, memset(dis, 0, sizeof dis);
	k = fndrt(1, 0, 0, 0), build(k);
	for(calc(i), i += 1; i <= n; ++i)
		f[i] = rd() ^ ans % mod, j = rd(), val[i] = rd(), calc(i);
	return 0;
}

感谢阅读。

辛苦管理员审核,若有问题烦请指出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值