【NOI模拟赛】为NOI加点料(重链剖分,线段树)

题目背景

OID 在做NOI2021的第一题轻重边时,随便码了个改造版动态树就过了,发现正解是两个 l o g log log 的板子树剖。

祂觉得很不服气,这种简单题让祂失去了一次完美和正解碰撞的快感,于是打算在这道题的基础上加点料,出了一道改得很恶心的板子树剖。

既然说是板子了,OID便大发慈悲地将数据范围调至 1 0 5 10^5 105 了。

题面

给定一棵 n n n 个点以 1 1 1 为根的有根树,边有颜色,初始时全为白色。

接下来有三种操作:

1 u

表示修改 u u u 到根的路径上的边的颜色,具体修改方式如下:

  • 首先,将从 u u u 到根的链拉出来,即为 l i s 1 , l i s 2 , . . . , l i s m lis_1,lis_2,...,lis_m lis1,lis2,...,lism,其中 l i s 1 = u , l i s m = 1 , ∀ 1 ≤ i < m , l i s i + 1 = f a l i s i lis_1=u,lis_m=1,\forall 1\leq i<m,lis_{i+1}=fa_{lis_i } lis1=u,lism=1,1i<m,lisi+1=falisi
  • 然后,除去 u u u 到根路径上的边,将其余与 l i s 1 , l i s 3 , l i s 5 , . . . lis_1,lis_3,lis_5,... lis1,lis3,lis5,... 相连的边染成黑色,将与 l i s 2 , l i s 4 , l i s 6 , . . . lis_2,lis_4,lis_6,... lis2,lis4,lis6,... 相连的边染成白色。
  • 最后,对于在 u u u 到根路径上的边,将 ( l i s 1 , l i s 2 ) , ( l i s 3 , l i s 4 ) , ( l i s 5 , l i s 6 ) , . . . (lis_1,lis_2),(lis_3,lis_4),(lis_5,lis_6),... (lis1,lis2),(lis3,lis4),(lis5,lis6),... 染成黑色,将 ( l i s 2 , l i s 3 ) , ( l i s 4 , l i s 5 ) , ( l i s 6 , l i s 7 ) , . . . (lis_2,lis_3),(lis_4,lis_5),(lis_6,lis_7),... (lis2,lis3),(lis4,lis5),(lis6,lis7),... 染成白色。
2 u

表示查询 u u u 到根路径上的黑边的个数。

3 u

表示查询 u u u 子树内黑边的个数。

输入格式

第一行一个整数 n n n 表示树的节点个数。

接下来一行 n − 1 n-1 n1 个整数,第 i i i 个整数 f a i + 1 fa_{i+1} fai+1 表示第 i + 1 i+1 i+1 个点的父亲。

接下来一个整数 q q q 表示操作个数。

接下来 q q q 行,每行两个整数,含义如题面所示。

输出格式

对于每个 2 , 3 2,3 2,3 操作,输出一个整数表示答案。

样例

7
1 2 1 1 4 6
10
3 2
1 4
1 4
3 4
1 6
3 4
3 1
1 2
1 4
2 5
0
1
2
4
0

数据范围

1 ≤ n , q ≤ 1 0 5 , f a i < i 1\leq n,q\leq 10^5,fa_i<i 1n,q105,fai<i

题解

这题我们分两个部分维护,操作 2 和操作 3 。

操作二,我们发现和NOI2021轻重边很像,我们可以给点覆盖点权,

具体地,我们树链剖分,打区间覆盖标记 t a g tag tag每次操作的 t a g tag tag 至少增加 2),表示该区间中奇数位置标记 t a g tag tag偶数位置标记 t a g ⊕ 1 tag\oplus1 tag1 ,加该标记时可以很方便地计算区间中的黑边个数,该标记也十分方便下传和合并。

计算两个点之间的边的颜色的时候这样算:如果两个点的标记异或和为 1,那么以右边的标记为准,否则以较大标记为准。若标记为奇数则为黑边,否则为白边。这个规则方便在线段树上合并。

这样每次修改链的时候,刚好就把所有邻接的边都“修改”了,且链上部分的颜色也正确。

操作三,我们会发现一个结论:每个点连向儿子的黑边个数只会是儿子个数儿子个数-11 三种。且每次修改的时候会将一个点黑边变成儿子个数,剩余的一条链上的点依次赋为 1儿子数-11儿子数-1,……

这个过程也可以用懒标记维护,因为树的形态不变,每个点的儿子数不变,每段区间内每一次集体赋值也只有两种。

时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

毛毛虫做法看不懂啊

CODE

#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<random>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_map>
#pragma GCC optimize(2)
using namespace std;
#define MAXN 100005
#define LL long long
#define ULL unsigned long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define PR pair<int,int>
int xchar() {
	static const int maxn = 1000000;
	static char b[maxn];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
	if(pos == len) return -1;
	return b[pos ++];
}
#define getchar() xchar()
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<<1) + (x<<3) + (s^48);s = getchar();}
	return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
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 hd[MAXN],nx[MAXN],fa[MAXN],ind[MAXN];
int siz[MAXN],son[MAXN],tp[MAXN],d[MAXN];
int dfn[MAXN],rr[MAXN],tim,id[MAXN];
void dfs0(int x) {
	d[x] = d[fa[x]] + 1;
	siz[x] = 1; son[x] = 0;
	for(int y = hd[x];y;y = nx[y]) {
		dfs0(y);
		siz[x] += siz[y];
		if(siz[y] > siz[son[x]]) son[x] = y;
	}
	return ;
}
void dfs1(int x) {
	if(son[fa[x]] == x) tp[x] = tp[fa[x]];
	else tp[x] = x;
	dfn[x] = ++ tim; id[tim] = x;
	if(son[x]) dfs1(son[x]);
	for(int y = hd[x];y;y = nx[y]) {
		if(y != son[x]) {
			dfs1(y);
		}
	} rr[x] = tim;
	return ;
}
struct it{
	int le,l,r,bl;
	it(){le=l=r=bl=0;}
	void fl(int tg) {
		l = tg; r = tg^(le&1);
		if(tg&1) bl = le>>1;
		else bl = (le+1)>>1;
	}
}tre[MAXN<<2];
it merg(it a,it b) {
	if(a.le < 0) return b;
	if(b.le < 0) return a;
	a.le += b.le+1; a.bl += b.bl;
	if(a.r == (b.l^1)) a.bl += (b.l&1);
	else a.bl += (max(a.r,b.l)&1);
	a.r = b.r; return a;
}
int sm[MAXN<<2],sm1[MAXN<<2],sm2[MAXN<<2],sz[MAXN<<2];
int lz1[MAXN<<2],lz2[MAXN<<2],M,bt;
inline void upd(int s) {
	if(s>=M) return ;
	tre[s] = merg(tre[s<<1],tre[s<<1|1]);
	sm[s] = sm[s<<1] + sm[s<<1|1];
	return ;
}
inline void sts(int s,int tg) {
	lz2[s] = tg;
	if(tg) sm[s] = sm1[s];
	else sm[s] = sm2[s];
	return ;
}
inline void pushdown(int s) {
	if(s>=M) return ;
	if(lz1[s]) {
		tre[s<<1].fl(lz1[s<<1] = lz1[s]);
		tre[s<<1|1].fl(lz1[s<<1|1] = lz1[s]^((sz[s<<1]&1) ? 1:0));
		lz1[s] = 0;
	}
	if(lz2[s] >= 0) {
		sts(s<<1,lz2[s]); sts(s<<1|1,lz2[s]^((sz[s<<1]&1) ? 1:0));
		lz2[s] = -1;
	} return ;
}
void maketree(int n) {
	M=1; bt=0; while(M<n+2) M<<=1,bt++;
	memset(lz2,-1,sizeof(lz2));
	for(int i = 1;i <= n;i ++) sm1[i+M] = ind[id[i]]-1,sm2[i+M] = 1,sz[i+M] = 1;
	for(int s = M-1;s > 0;s --) {
		sz[s] = sz[s<<1] + sz[s<<1|1];
		if(sz[s<<1]&1) sm1[s] = sm1[s<<1] + sm2[s<<1|1],sm2[s] = sm2[s<<1] + sm1[s<<1|1];
		else sm1[s] = sm1[s<<1] + sm1[s<<1|1],sm2[s] = sm2[s<<1] + sm2[s<<1|1];
		upd(s);
	} return ;
}
void addp(int x,int y) {
	int s = M+x;
	for(int i = bt;i > 0;i --) pushdown(s>>i);
	sm[s] = y; s >>= 1;
	for(;s > 0;s >>= 1) sm[s] = sm[s<<1] + sm[s<<1|1];
	return ;
}
void addseg(int l,int r,int tg) {
	if(l > r) return ;
	int ls = 0,rs = r-l+1,s = M+l-1,t = M+r+1;
	for(int i = bt;i > 0;i --) pushdown(s>>i),pushdown(t>>i);
	for(;s || t;s >>= 1,t >>= 1) {
		upd(s); upd(t);
		if((s>>1) ^ (t>>1)) {
			if(!(s&1)) {
				tre[s^1].fl(lz1[s^1] = tg^(ls&1));
				sts(s^1,(tg^ls)&1); ls += sz[s^1];
			}
			if(t & 1) {
				rs -= sz[t^1]; sts(t^1,(tg^rs)&1);
				tre[t^1].fl(lz1[t^1] = tg^(rs&1));
			}
		}
	} return ;
}
it findtree(int l,int r) {
	it ls = it(),rs = it(); ls.le = rs.le = -1;
	if(l > r) return ls;
	int s = M+l-1,t = M+r+1;
	for(int i = bt;i > 0;i --) pushdown(s>>i),pushdown(t>>i);
	for(;(s>>1) ^ (t>>1);s >>= 1,t >>= 1) {
		if(!(s&1)) ls = merg(ls,tre[s^1]);
		if(t & 1) rs = merg(tre[t^1],rs);
	} return merg(ls,rs);
}
int findsum(int l,int r) {
	if(l > r) return 0;
	int as = 0,s = M+l-1,t = M+r+1;
	for(int i = bt;i > 0;i --) pushdown(s>>i),pushdown(t>>i);
	for(;(s>>1) ^ (t>>1);s >>= 1,t >>= 1) {
		if(!(s&1)) as += sm[s^1];
		if(t & 1) as += sm[t^1];
	} return as;
}
void setline(int x,int tg) {
	addseg(dfn[x],dfn[x],tg^1);
	addp(dfn[x],ind[x]);
	int de = d[x]-1; x = fa[x];
	while(x) {
		addseg(dfn[tp[x]],dfn[x],tg ^ ((de - d[tp[x]]) & 1));
		x = fa[tp[x]];
	} return ;
}
int findline(int x) {
	it as = it();as.le = -1;
	while(x) {
		as = merg(findtree(dfn[tp[x]],dfn[x]),as);
		x = fa[tp[x]];
	} return as.bl;
}
int main() {
	freopen("chain.in","r",stdin);
	freopen("chain.out","w",stdout);
	n = read();
	for(int i = 2;i <= n;i ++) {
		s = fa[i] = read(); ind[s] ++;
		nx[i] = hd[s]; hd[s] = i;
	}
	dfs0(1); dfs1(1);
	maketree(n);
	m = read();
	for(int i = 1;i <= m;i ++) {
		k = read(); s = read();
		if(k == 1) {
			setline(s,i*2);
		}
		else if(k == 2) {
			AIput(findline(s),'\n');
		}
		else {
			AIput(findsum(dfn[s],rr[s]),'\n');
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值