奇怪的树
奇怪的树
前言:在题解底部我还贴了篇 \(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)\)