【学习笔记】「北大集训 2021」经典游戏

我觉得很厉害。要是考场上能把这道题切了的话数据结构的水平肯定是不低的。

考虑简化版问题:如果只询问一个点的答案怎么做。

注意,我这么做是有风险的。我把战线拉长了。不过当然,如果连简化版的问题都做不了,那何谈正解?幸运的是,这确实是一道数据结构多合一的题。

考虑 长链剖分 。那么在 x x x节点上加入棋子时,子树外的点就异或上 x x x子树的最大深度,如果以 x x x为根的最长链在重儿子上面,那么就给除了重儿子外的子树打标记,注意到 dfn \text{dfn} dfn序是连续的 ,可以直接打标;对于重儿子也可以直接对整颗树打标,只需处理出 x x x去掉重儿子后的最长链长度即可;如果最长链在父亲上,那么直接对 x x x整颗子树修改即可。

发现了吗?经过细致分析,我们发现这道题其实并不复杂。当然要建立在想到长链剖分的基础上。

这题更神奇的地方在于,让我们求 dist(x,v) ≤ 1 \text{dist(x,v)}\le 1 dist(x,v)1的所有根的答案。这是个非常恼人的限制,因为你知道会被菊花图卡,但是不知道会被卡成多少分。

有没有严格的做法呢?答案是有的。但是我一定想不到。 但是需要用到非常高级的技巧。其实说白了,询问可以拆分成 x x x x x x的父亲, x x x的重儿子以及 x x x的所有轻儿子。如果一次插入影响到点的数目是 O ( 1 ) O(1) O(1)那么我们的目的就达到了。

考虑这样一个问题,如何用字典树维护 c i ⊕ x > d i c_i\oplus x>d_i cix>di的所有点对?这个问题也非常具有迷惑性。因为 d i d_i di是定值, x x x又是每次询问给定的,那么维护 c i c_i ci然后在 trie \text{trie} trie树上查不就完了?并且 trie \text{trie} trie树查询的复杂度也是 log ⁡ n \log n logn的。这样分析下来觉得越来越有道理了,但是考场上完全想不到这里来啊???

初始化的时候 c i c_i ci都是定值。手动分讨一波,如果最长链在重儿子上面那么 x x x的所有轻儿子都异或上同一个数,直接在 x x x上打标就完了;如果最长链在父亲上面那么轻儿子还是异或上同一个数。事实上可以发现,任意时刻轻儿子的标都和这个点上的标是一样的,唯一的例外是当插入的点就是这个轻儿子的情况。但是正如前所说,修改影响到的节点数目是 O ( 1 ) O(1) O(1)的,所以直接在 trie \text{trie} trie树上暴力修改就行。然后就做完了。

所以发现了吗?这种题逻辑链条太长了。其实思维难度并不大。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

要考虑的细节挺多了。所以代码先咕了。

代码写错了好多地方,所以调了好久,看来我还是太菜了。。。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
using namespace std;
const int N=1e6+5;
int type;
int n,m,sz[N],dp[N],dp2[N],dpfa[N],son[N],bitxor[N],dfn[N],num;
int trie[N*25][2],tot,rt[N],fa[N],sztree[N*25],c[N];
vector<int>G[N];
void dfs(int u,int topf){
	sz[u]=1,fa[u]=topf;
	for(auto v:G[u]){
		if(v!=topf){
			dfs(v,u),sz[u]+=sz[v];
			if(!son[u]||dp[v]>dp[son[u]]){
				son[u]=v;
			}
			if(dp[v]+1>dp[u])dp2[u]=dp[u],dp[u]=dp[v]+1;
			else if(dp[v]+1>dp2[u])dp2[u]=dp[v]+1;
		}
	}
}
//fixed
void ins(int it,int val,int f){
	for(int i=20;i>=0;i--){
		int p=val>>i&1;
		if(!trie[it][p])trie[it][p]=++tot;
		it=trie[it][p];
		sztree[it]+=f;
	}
}
int query(int it,int x,int y){
	int tot=0;
	for(int i=20;i>=0;i--){
		if(y>>i&1){
			it=trie[it][(x>>i&1)^1];
		}
		else {
			tot+=sztree[trie[it][(x>>i&1)^1]];
			it=trie[it][x>>i&1];
		}
	}
	return tot;
}
//fixed
void dfs2(int u){
	dfn[u]=++num;
	if(son[u])dfs2(son[u]);
	rt[u]=++tot;
	for(auto v:G[u]){
		if(!dfn[v]){
			dfs2(v);
			ins(rt[u],0,1);
		}
	}
}
void dfs3(int u,int topf){
	for(auto v:G[u]){
		if(v!=topf){
			if(dp[v]+1==dp[u]){
				dpfa[v]=dp2[u]+1;
			}
			else{
				dpfa[v]=dp[u]+1;
			}
			if(dpfa[v]>dp[v])dp2[v]=dp[v],dp[v]=dpfa[v];
			else if(dpfa[v]>dp2[v])dp2[v]=dpfa[v];
			dfs3(v,u);
		}
	}
}
//fixed
void add(int x,int y){
	assert(x);
	for(;x<=n;x+=x&-x)bitxor[x]^=y;
}
void addseq(int l,int r,int x){
	assert(l<=r);
	add(l,x),add(r+1,x);
}
//fixed
int getval(int x){
	int tot=0;
	for(x=dfn[x];x;x-=x&-x)tot^=bitxor[x];
	return tot;
}
int solve(int x){
	return x&&getval(x)>dp[x];
}
void update(int x){
	//fixed
	if(son[x]&&dp[x]!=dpfa[x]){
		addseq(1,n,dp[x]);
		addseq(dfn[son[x]],dfn[son[x]]+sz[son[x]]-1,dp[x]^dp2[x]);
	}
	else{
		addseq(1,n,dp2[x]);
		addseq(dfn[x],dfn[x]+sz[x]-1,dp2[x]^dp[x]);
		if(fa[x]&&son[fa[x]]!=x){
			ins(rt[fa[x]],c[x],-1);
			c[x]^=dp[x]^dp2[x];
			ins(rt[fa[x]],c[x],1);
		}
		
	}
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
	cin>>type>>n>>m;
	//fixed
	for(int i=1;i<n;i++){
		int u,v;cin>>u>>v;
		G[u].pb(v),G[v].pb(u);
	}
	dfs(1,0),dfs2(1);
	dfs3(1,0);
	for(int i=1;i<=n;i++){
		int x;cin>>x;
		if(x&1)update(i);
	}
	for(int i=1;i<=m;i++){
		int x,y;cin>>x>>y;
		update(x);
		int res=solve(y)+solve(son[y])+solve(fa[y])+query(rt[y],getval(y),dp[y]+1);
		cout<<res<<"\n";
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值