奇怪的树

奇怪的树

题面:5NJqqU.png


前言:在题解底部我还贴了篇 \(16\) 届学长的题解。


​ 对于操作 \(1\)。注意到一棵树是一幅 二分图,所以可以分成两个集合,然后打标记,又因为一个集合修改两次等于没修改,所以我们同时维护 \(4\) 种情况的树就可以解决了。

​ 对于操作 \(2\)。单点修改,具体实现得看我们的操作 \(3\)

​ 对于操作 \(3\)。设当前查询的点为 \(p\)。那么答案就是顺着 \(p\) 的父亲往上爬,设当前爬到的节点是 \(x\)。那么 \(v_x=x*(siz_p-siz_{son})\)\(son\) 表示包含 \(p\) 的节点的子树。

​ 对于这个东西,我们不好直接维护。因为关系到儿子节点以及自己的编号。考虑树链剖分,这样可以分开轻儿子和重儿子讨论。

​ 我们设 \(f_i\) 表示 \(i\) 这个节点所有轻儿子以及他自己是黑色的个数 \(\times\) \(i\) 。那么答案就是多条重链逐渐累加上去,同时,跳到重链顶的父亲时,我们加上这个节点子树里黑色点的个数 \(\times\) 子树的编号,因为这条重链下端的部分并没有记录到答案中。而对于重链顶的父亲往上到重链顶的父亲的重链顶仍然是累加 \(f\) 值。(可能有点绕)。由于重链顶的父亲的子树是包括重链顶所在子树的个数的,这部分我们已经统计过了,所以要减去重复的。也就是重链顶的子树个数 \(\times\) 重链顶的父亲的编号。

​ 代码并不长:

#include<bits/stdc++.h>
#define ll long long
#define lowbit(i) (i&(-i))
using namespace std;
const int MAXN = 1e5+5;
int n,m,a[MAXN];
struct E
{
	int to,pre;
}e[MAXN<<1];
int tot_E,head[MAXN],dep[MAXN],siz[MAXN],top[MAXN],idx[MAXN][2];
int f[MAXN],color[MAXN],now[2],son[MAXN],totdfn;
void upd(ll *t,int pos,int x){for(int i=pos;i<=totdfn;i+=lowbit(i))t[i]+=x;}
ll query(ll *t,int pos){ll ans=0;for(int i=pos;i;i-=lowbit(i)) ans+=t[i];return ans;}	
struct Bit
{
	ll t[2][MAXN];
}BIT[2][2];
void add(int u,int v)
{
	e[++tot_E]=E{v,head[u]};
	head[u]=tot_E;
}
void dfs1(int p,int fa)
{
	siz[p]=1;dep[p]=dep[fa]+1;f[p]=fa;
	for(int i=head[p];i;i=e[i].pre)
	{
		int to=e[i].to;
		if(to==fa) continue;
		dfs1(to,p);
		siz[p]+=siz[to];
		if(siz[to]>siz[son[p]]) son[p]=to;
	}
}
void dfs2(int p,int fa,int t)
{
	idx[p][0]=++totdfn;top[p]=t;
	if(son[p]) dfs2(son[p],p,t);
	for(int i=head[p];i;i=e[i].pre)
	{
		int to=e[i].to;
		if(to==fa||to==son[p]) continue;
		dfs2(to,p,to);
	}
	idx[p][1]=totdfn;
}
void UPD(int sta1,int sta2,int p,int v)
{
	upd(BIT[sta1][sta2].t[0],idx[p][0],v);
	while(p)
	{
		upd(BIT[sta1][sta2].t[1],idx[p][0],v*p);
		p=f[top[p]];
	}
}
ll Q(ll *t,int le,int ri)
{
	if(le>ri) return 0;
	return query(t,ri)-query(t,le-1);
}
ll Get_ans(int x)
{
	ll ans=0;
	while(x)
	{
		ans+=Q(BIT[now[0]][now[1]].t[1],idx[top[x]][0],idx[x][0]-1)+Q(BIT[now[0]][now[1]].t[0],idx[x][0],idx[x][1])*x;
		ans-=Q(BIT[now[0]][now[1]].t[0],idx[top[x]][0],idx[top[x]][1])*f[top[x]];
		x=f[top[x]];
	}
	return ans;
}
int main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	for(int i=1;i<n;++i)
	{
		int u,v;
		scanf("%d %d",&u,&v);
		add(u,v);add(v,u);
	}
	dfs1(1,0);dfs2(1,0,1);
	for(int i=1;i<=n;++i)
	{
		color[i]=(dep[i]-1)&1;
		for(int j=0;j<2;++j)
		{
			if(color[i]) UPD(j,0,i,a[i]),UPD(j,1,i,a[i]^1);
			else UPD(0,j,i,a[i]),UPD(1,j,i,a[i]^1);
		}
	}
	for(int i=1;i<=m;++i)
	{
		int opt,x;
		scanf("%d %d",&opt,&x);
		if(opt==1) now[color[x]^1]^=1;
		else if(opt==2)
		{
			for(int j=0;j<2;++j)
			{
				if(color[x]) UPD(j,0,x,-a[x]),UPD(j,1,x,-(a[x]^1));
				else UPD(0,j,x,-a[x]),UPD(1,j,x,-(a[x]^1));
			}
			a[x]^=1;
			for(int j=0;j<2;++j)
			{
				if(color[x]) UPD(j,0,x,a[x]),UPD(j,1,x,a[x]^1);
				else UPD(0,j,x,a[x]),UPD(1,j,x,a[x]^1);
			}
		}
		else printf("%lld\n",Get_ans(x));
	}
	return 0;
}

周松涛学长的题解:

Analysis

每次做1操作相当于把所有的奇数(偶数)点的颜色翻转,在询问的时候所有\(a\)的祖先对答案的贡献就是其本身,所有\(a\)的子孙对答案的贡献就是\(a\),考虑将点集按照奇偶位置分开,并且奇偶分别维护一个黑色点的前缀和与后缀和、白色点的前缀和与后缀和,并对奇数(偶数)点集分别打标记;修改单点时将其从一个集合移到另一个集合;查询时判断当前的黑色点是初始的黑色点还是白色点(全集的标记),分别查询即可.

延续链上的操作,考虑将整棵树轻重链剖分,这样每次向上跳重链,对于区间\([L,R]\),发现需要以下信息:

  • \(\sum\)(通过轻链到达的所有子树的黑点个数+本身是否为黑点)
  • \(\sum\)((通过轻链到达的所有子树的黑点个数+本身是否为黑点)*其编号)

这样,每次从\(u\)向上跳到一条链上,不妨记为\(x\),则对答案的贡献分别是:

  • \(x\)下方的重链上的\(\sum\)(通过轻链到达的所有子树的黑点个数+本身是否为黑点),即,\(x\)子树内的黑点个数,这里注意要把\(u\)子树中的贡献减去,因为\(u\)也是\(x\)通过轻边到达的点.
  • \(x\)上方的重链上的\(\sum\)((通过轻链到达的所有子树的黑点个数+本身是否为黑点)*其编号)

由于存在操作1,仿照链式写法,分别维护奇数偶数黑色白色的情况,

修改单个节点的时候,由于维护的所有点通过轻链到达的子树的信息,那么只需要对所有的重链顶端的点的父亲节点和 \(u\) 节点进行修改,按理应该先将所有的旧的值先减掉,但是这里的答案满足可加性,可以用树状数组维护,不妨直接考虑答案的数量变更,即,对所有节点的更新都相当于在其子树内加一个黑点或删一个黑点.

总复杂度\(O(mlog^2n+nlogn)\)

posted @ 2021-10-18 11:12  夜空之星  阅读(96)  评论(0编辑  收藏  举报