【NOI P模拟赛】校门外歪脖树上的鸽子(树链剖分)

题面

在这里插入图片描述
在这里插入图片描述


2 ≤ n ≤ 2 × 1 0 5 , 1 ≤ m ≤ 2 × 1 0 5 , 1 ≤ l ≤ r ≤ n , 1 ≤ d ≤ 1 0 8 2 ≤ n ≤ 2 × 10^5,1 ≤ m ≤ 2 × 10^5,1 ≤ l ≤ r ≤ n,1 ≤ d ≤ 10^8 2n2×1051m2×1051lrn1d108

题解

其实核心思路很简单:我们把原树看成一棵zkw线段树,而不是普通线段树,这样我们修改以及询问的对象就是左哨兵链的右兄弟 以及 右哨兵链的左兄弟,这两条链从叶子一直延申到 lca 的孙子辈

树链剖分分别维护记录每个点 右兄弟 和 左兄弟 的两棵线段树,时间复杂度 O ( ( n + m ) log ⁡ 2 n ) O((n+m)\log^2n) O((n+m)log2n)

剩下的就是一些细节了。

首先预处理两棵线段树的基本信息,需要让左儿子记录右儿子的区间长度,右儿子记录左儿子的区间长度,然后再根据子树大小(区间长度)重链剖分。剖分后保留原先记录的区间长度不变,也就是说原先的右儿子可能 dfs 序还比左儿子小,但是仍然记录原先左儿子的区间长度、在“左兄弟”线段树上做贡献,并扣牢“右儿子”的帽子。修改以及询问的时候,不因为左右端点实际 dfs 序的颠倒而互换。

针对左右边界,可以加两个哨兵,也可以直接特判。

控制访问到 lca 的孙子辈,比较难且常数较大,我们可以记录儿子辈,然后访问完后把儿子辈的修改 / 贡献撤回。

CODE

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<random>
#include<vector>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 400005
#define LL long long
#define ULL unsigned long long
#define DB double
#define lowbit(x) (-(x) & (x))
#define ENDL putchar('\n')
#define FI first
#define SE second
LL read() {
    LL f=1,x=0;int s = getchar(); 
    while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
    while(s >= '0' && s <= '9') {x = (x<<3) + (x<<1) + (s^48); s = getchar();}
    return f*x;
}
LL read(int first_c) {
    LL f=1,x=0;int s = first_c; 
    while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
    while(s >= '0' && s <= '9') {x = (x<<3) + (x<<1) + (s^48); s = getchar();}
    return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar('0'+(x%10));}
void putnum(LL x) {
    if(!x) {putchar('0');return ;}
    if(x<0) {putchar('-');x = -x;}
    return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}

int n,m,s,o,k;
int ls[MAXN],rs[MAXN];
int l[MAXN],r[MAXN];
int fa[MAXN];
int d[MAXN],dfn[MAXN],rr[MAXN],tp[MAXN],tim,id[MAXN];
int lw[MAXN],rw[MAXN];
void dfs0(int x) {
	d[x] = d[fa[x]] + 1;
	tim = 0;
	if(!ls[x]) return ;
	dfs0(ls[x]); dfs0(rs[x]);
	if(l[ls[x]] > l[rs[x]]) swap(ls[x],rs[x]);
	l[x] = l[ls[x]]; r[x] = r[rs[x]];
	int A,B;
	A = lw[rs[x]] = r[ls[x]] - l[ls[x]] + 1;
	B = rw[ls[x]] = r[rs[x]] - l[rs[x]] + 1;
	if(A < B) swap(ls[x],rs[x]);
	return ;
}
void dfs(int x) {
	if(x == ls[fa[x]]) tp[x] = tp[fa[x]];
	else tp[x] = x;
	dfn[x] = ++ tim; id[tim] = x;
	if(ls[x]) dfs(ls[x]);
	if(rs[x]) dfs(rs[x]);
	rr[x] = tim;
	return ;
}
struct it{
	int ls,rs;
	int ct;
	LL sm,lz;
	it(){ls=rs=ct=sm=lz=0;}
}tre[MAXN<<2];
int cnt;
void addd(int a,LL d) {
	tre[a].sm += tre[a].ct *1ll* d;
	tre[a].lz += d; return ;
}
void update(int a) {
	tre[a].sm = tre[tre[a].ls].sm + tre[tre[a].rs].sm;
	tre[a].ct = tre[tre[a].ls].ct + tre[tre[a].rs].ct;
	return ;
}
void pushdown(int a) {
	if(tre[a].lz) {
		addd(tre[a].ls,tre[a].lz);
		addd(tre[a].rs,tre[a].lz);
		tre[a].lz = 0;
	}return ;
}
int maketree(int *p,int l,int r) {
	int a = ++ cnt; tre[a] = it();
	if(l == r) {tre[a].sm = 0; tre[a].ct = p[id[l]];}
	else {
		int md = (l + r) >> 1;
		tre[a].ls = maketree(p,l,md);
		tre[a].rs = maketree(p,md+1,r);
		update(a);
	}
	return a;
}
void addtree(int a,int l,int r,int al,int ar,int d) {
	if(l > r || al > r || ar < l) return ;
	if(al >= l && ar <= r) {
		addd(a,d); return ;
	}
	int md = (al + ar) >> 1; pushdown(a);
	addtree(tre[a].ls,l,r,al,md,d); addtree(tre[a].rs,l,r,md+1,ar,d);
	update(a); return ;
}
LL findtree(int a,int l,int r,int al,int ar) {
	if(l > r || al > r || ar < l) return 0ll;
	if(al >= l && ar <= r) return tre[a].sm;
	int md = (al + ar) >> 1; pushdown(a);
	return findtree(tre[a].ls,l,r,al,md) + findtree(tre[a].rs,l,r,md+1,ar);
}
LL FL = 0;
int RT1,RT2;
void addline(int a,int b,int y) {
	if(a < 1 && b > n) {
		FL += y *1ll* n; return ;
	}
	if(a < 1) {
		while(b) {
			addtree(RT1,dfn[tp[b]],dfn[b],1,tim,y);
			b = fa[tp[b]];
		} return ;
	}
	if(b > n) {
		while(a) {
			addtree(RT2,dfn[tp[a]],dfn[a],1,tim,y);
			a = fa[tp[a]];
		} return ;
	}
	int ld = 0,rd = 0;
	while(tp[a] != tp[b]) {
		if(d[tp[a]] > d[tp[b]]) {
			addtree(RT2,dfn[tp[a]],dfn[a],1,tim,y); ld = tp[a];
			a = fa[tp[a]];
		}
		else {
			addtree(RT1,dfn[tp[b]],dfn[b],1,tim,y); rd = tp[b];
			b = fa[tp[b]];
		}
	}
	if(d[a] <= d[b]) addtree(RT2,dfn[ld],dfn[ld],1,tim,-y);
	if(d[b] <= d[a]) addtree(RT1,dfn[rd],dfn[rd],1,tim,-y);
	if(d[a] < d[b]) addtree(RT1,dfn[a]+2,dfn[b],1,tim,y);
	if(d[b] < d[a]) addtree(RT2,dfn[b]+2,dfn[a],1,tim,y);
	return ;
}
LL findline(int a,int b) {
	if(a < 1 && b > n) {
		return FL;
	}
	LL ans = 0;
	if(a < 1) {
		while(b) {
			ans += findtree(RT1,dfn[tp[b]],dfn[b],1,tim);
			b = fa[tp[b]];
		} return ans;
	}
	if(b > n) {
		while(a) {
			ans += findtree(RT2,dfn[tp[a]],dfn[a],1,tim);
			a = fa[tp[a]];
		} return ans;
	}
	int ld = 0,rd = 0;
	while(tp[a] != tp[b]) {
		if(d[tp[a]] > d[tp[b]]) {
			ans += findtree(RT2,dfn[tp[a]],dfn[a],1,tim); ld = tp[a];
			a = fa[tp[a]];
		}
		else {
			ans += findtree(RT1,dfn[tp[b]],dfn[b],1,tim); rd = tp[b];
			b = fa[tp[b]];
		}
	}
	if(d[a] <= d[b]) ans -= findtree(RT2,dfn[ld],dfn[ld],1,tim);
	if(d[b] <= d[a]) ans -= findtree(RT1,dfn[rd],dfn[rd],1,tim);
	if(d[a] < d[b]) ans += findtree(RT1,dfn[a]+2,dfn[b],1,tim);
	if(d[b] < d[a]) ans += findtree(RT2,dfn[b]+2,dfn[a],1,tim);
	return ans;
}
int main() {
	freopen("pigeons.in","r",stdin);
	freopen("pigeons.out","w",stdout);
	n = read();m = read();
	for(int i = 1;i <= n;i ++) l[i] = r[i] = i;
	for(int i = n+1;i < (n<<1);i ++) {
		s = ls[i] = read();
		o = rs[i] = read();
		fa[s] = fa[o] = i;
	}
	int rt = 0;
	for(int i = n+1;i < (n<<1);i ++) {
		if(!fa[i]) rt = i;
	}
	dfs0(rt);
	dfs(rt);
	RT1 = maketree(lw,1,tim);
	RT2 = maketree(rw,1,tim);
	for(int i = 1;i <= m;i ++) {
		k = read();
		if(k == 1) {
			s = read();o = read();k = read();
			addline(s-1,o+1,k);
		}
		else {
			s = read();o = read();
			AIput(findline(s-1,o+1),'\n');
		}
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值