Logo KSCD_ 的博客

博客

P10107 题解

...
KSCD_
2025-12-01 12:56:39
Defection,Retribution,Testify.

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-03-03 21:34:55

考虑按位计算贡献,先考虑链的情况。考虑倍增,设 $f_{i,k}$ 表示 $[i,i+2^k)$ 区间的答案。转移时先累加两个小区间的贡献,另外后半部分的第 $(k-1)$ 位要异或 $1$,因此设 $g_{i,k}$ 表示前 $i$ 个数第 $k$ 位若异或 $1$ 会产生的总变化量。统计时若其该位为 $1$ 则贡献 $-1$,否则贡献 $1$。通过差分计算变化量,最终转移为 $f_{i,k}=f_{i,k-1}+f_{i+2^{k-1},k-1}+2^{k-1}\times(g_{i+2^k-1}-g_{i+2^{k-1}-1})$。统计答案时一直向后跳并累加即可。

考虑把这东西搬到树上,但由于子树内在某一层上的点不止一个,只能在 $i$ 节点本身上记录信息。所以设 $f_{i,j,k}$ 表示 $i$ 子树内深度在 $[d_i+j,d_i+j+2^k)$ 内所有点的贡献和。可以发现这个 $(i,j)$ 很能长链剖分,重儿子直接继承,轻儿子对应位置直接累加。但是这样没有计算 $f_{i,0}$,考虑怎么计算这个。

那么先把 $a_i$ 加到 $f_{i,0}$ 里。假如从子树 $v$ 给 $f_{i,0}$ 更新,设 $s_k$ 表示 $v$ 子树内对 $f_{i,0,k}$ 的贡献,有 $s_0=0$。发现 $s_k$ 可以用 $s_{k-1}$ 拼上 $f_{v,2^{k-1}-1,k-1}$ 得到,这里的式子应该和链的情况一样,实际实现时由于 $s_k$ 只从 $s_{k-1}$ 转移,开一个变量,一直更新就行。

那么问题就在于还需要一个记录贡献变化量的数组 $g$ 来辅助更新。链上我们是用前缀和维护的,但是树上长剖不好搞前缀和,所以考虑转而计算后缀和。具体地设 $g_{i,j,k}$ 表示 $i$ 子树内深度不低于 $d_i+j$ 的所有点权第 $k$ 位若异或 $1$ 会产生的总变化量,这就可以直接对应位累加了,只需要在 $g_{i,0}$ 上额外加入 $a_i$ 的贡献即可。

所有的转移和统计答案都跟链上类似,把每个询问扔到对应节点上处理即可。只是要格外注意在 $g$ 上差分时不能超出当前节点的管辖范围,有比较多的细节。时间复杂度 $O((n+q)\log n)$。

#include<iostream>
#include<vector>
#define pii pair<int,int>
#define ll long long
using namespace std;
const int N=1e6+10;
const int K=20+3;
int n,q,v[N],d[N],son[N]; ll res[N];
vector <int> e[N];
vector <pii> a[N];
struct nod{ll f[K],g[K];}temp[N],*f[N],*cur=temp;
void dfs(int u)
{
	d[u]=0;
	for(int v:e[u])
	{
		dfs(v),d[u]=max(d[u],d[v]);
		if(d[v]>d[son[u]]) son[u]=v;
	}
	d[u]++;
}
void merg(int u,int v)
{
	for(int j=0;j<20;j++) f[u][0].g[j]+=f[v][0].g[j];
	ll s=0;
	for(int p=0,k=1;k<20;p+=(1<<(k-1)),k++)
	{
		if(p<d[v]) s+=f[v][p].f[k-1]+(1<<(k-1))*f[v][p].g[k-1];
		if(p+(1<<(k-1))<d[v]) s-=(1<<(k-1))*f[v][p+(1<<(k-1))].g[k-1];
		f[u][0].f[k]+=s;
	}
}
void dfsb(int u)
{
	for(int j=0;j<20;j++) f[u][0].f[j]=v[u],f[u][0].g[j]=(((v[u]>>j)&1)?-1:1);
	if(son[u]) f[son[u]]=f[u]+1,dfsb(son[u]),merg(u,son[u]);
	for(int v:e[u]) if(v!=son[u])
	{
		f[v]=cur,cur+=d[v],dfsb(v),merg(u,v);
		for(int j=0;j<d[v];j++) for(int k=0;k<20;k++) 
			f[u][j+1].f[k]+=f[v][j].f[k],f[u][j+1].g[k]+=f[v][j].g[k];
	}
	for(pii te:a[u])
	{
		int x=min(te.first,d[u]-1),id=te.second;
		for(int p=0,k=19;k>=0;k--)
		{
			if(p+(1<<k)-1>x) continue;
			res[id]+=f[u][p].f[k],p+=(1<<k);
			if(p<d[u]) res[id]+=(1<<k)*f[u][p].g[k];
			if(x+1<d[u]) res[id]-=(1<<k)*f[u][x+1].g[k];
		}
	}
}
signed main()
{
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n,d[0]=-1;
	for(int i=1;i<=n;i++) cin>>v[i];
	for(int i=2,x;i<=n;i++) cin>>x,e[x].push_back(i);
	cin>>q;
	for(int i=1;i<=q;i++)
	{
		int x,k; cin>>x>>k;
		a[x].push_back({k,i});
	}
	dfs(1),f[1]=cur,cur+=d[1],dfsb(1);
	for(int i=1;i<=q;i++) cout<<res[i]<<'\n';
	return 0;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。