题意:
给出一棵有$n$个节点的树,每个节点有亮/暗的一个属性。$m$次操作,每次给出一个点$x$,对$x$以及与$x$相邻的点取反,或者询问这些点里一共有多少个是亮着的。
$1\le x\le n \le 10^6$
分析一:
按BFS序将树写成一个序列,那么每组兄弟节点一定相邻,线段树维护单点修改,单点查询,区间修改,区间查询即可。
实现一(略)
分析二:
每个点的属性只跟父亲、自己、孩子的修改次数奇偶有关。每次修改影响不会超过两条边的半径。
考虑一个点$x$仅记录三条信息:
$l$:$x$的修改异或${son[x]}$的修改
$f$:$x$的修改
$s$:${son[x]}$里亮着的个数
那么一个点$x$的属性可以由$l[x]\oplus f[fa[x]]$得来。“$\oplus$”表示异或。
每次修改$x$仅操作$x$、$fa[x]$、$fa[fa[x]]$。
实现二(100分):
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #include<queue> #define IL inline #define UI unsigned int #define RI register int #define _1 first #define _2 second using namespace std; const int N=1e6; int n,m,a[N+3]; bool l[N+3],f[N+3]; int sz[N+3],s[N+3]; int main(){ scanf("%d",&n); for(int i=2;i<=n;i++) scanf("%d",&a[i]); memset(sz,0,sizeof sz); for(int i=2;i<=n;i++) sz[a[i]]++; memset(l,0,sizeof l); memset(f,0,sizeof f); memset(s,0,sizeof s); scanf("%d\n",&m); while(m--){ int t,x; scanf("%d%d",&t,&x); if(t==1){ s[x]=sz[x]-s[x]; l[x]^=1; f[x]^=1; if(x==1) continue; l[a[x]]^=1; (l[x]^f[a[x]])?(s[a[x]]++):(s[a[x]]--); if(a[x]==1) continue; (l[a[x]]^f[a[a[x]]])?(s[a[a[x]]]++):(s[a[a[x]]]--); } else { int s1=(a[x]==1)?l[a[x]]:(l[a[x]]^f[a[a[x]]]); int s2=(x==1)?l[x]:(l[x]^f[a[x]]); printf("%d\n",s1+s2+s[x]); } } return 0; }
小结:
记录/修改所有元素的精确值往往很麻烦。可以考虑把从属关系分类合并,用“粗略信息”回答询问。例如本题的$s$,和10.16发的《矩阵》题解里的求和。