给一棵n个结点的树,结点有黑白两色,一开始全为黑色.
对于q个操作,每个操作由两个整数op,u给出.
当op=0,将u点颜色反转.
当op=1,求与u点相连的点的个数(若两点及两点间路径上均为同色点,则两点相连,否则不相连),即求从u点往四周扩散的同色块大小.
(初学链剖,戳开了这个题,然后……d了一天的bug)
在网上找到了一篇题解,感觉思路非常好,然后就理解了一发.
对于每个同色块,选择将同色块大小存放在其顶端结点处,那么每次询问操作的时候只需要找到顶端结点即可.
- 如何寻找顶端结点?
因为若两点之间无断点,是连续的,那么区间的同色点个数应当和区间长度一致,用一个树状数组维护.对于某一个点而言与其所在链顶端所成区间进行比较,若个数一致,那么断点不在此重链上,若个数比区间长度小,那么断点在此重链上,对重链进行二分,用相同的方式判断.
那么当结点颜色改变时,如何修改和维护?
先找到当前点x向上能影响到的最浅结点f,对于x的父节点直至f点的父节点都要修改,而非修改x到f.
- 不修改x点的原因
之后若x点的颜色再度反转,那么x点上方的一些点要修改的值就应该是x点原先下面的同色结点数,而若修改后,值就变成0了.
- 要修改f父节点的原因
因为若反转f点的颜色,f点是不会修改的,那么之后询问f点的值则应该是修改之前的值,若不修改,值是错误的.
#include
#include
#include
using namespace std;
const int maxn=100000+10;
const int maxm=200000+10;
int read()
{
char ch=getchar();int ret=0;
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
return ret;
}
int fir[maxn],nxt[maxm],to[maxm],ecnt;
void add_edge(int u,int v)
{
nxt[++ecnt]=fir[u];fir[u]=ecnt;to[ecnt]=v;
nxt[++ecnt]=fir[v];fir[v]=ecnt;to[ecnt]=u;
}
int fa[maxn],sz[maxn],son[maxn];
void dfs1(int u,int p)
{
fa[u]=p;sz[u]=1;son[u]=0;
for(int i=fir[u];i;i=nxt[i]) if(to[i]!=p) {
int v=to[i];
dfs1(v,u);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
}
int top[maxn],dep[maxn],rnk[maxn],idx[maxn],id;//rnk 新编号映射旧编号 idx 旧编号映射新编号
void dfs2(int u,int t,int d)
{
top[u]=t;dep[u]=d;rnk[idx[u]=++id]=u;
if(son[u]) dfs2(son[u],t,d+1);
for(int i=fir[u];i;i=nxt[i])
if(to[i]!=fa[u]&&to[i]!=son[u]) dfs2(to[i],to[i],d+1);
}
#define lowbit(x) (x&-x)
int col[maxn],T[3][maxn],n,q;//col:0 black,1 white
//sum(T[0/1][u])表示u点向下的黑色/白色块大小 用于存放答案
//sum(T[2][u])-sum(T[2][v])表示以u~v的黑色点个数 用于二分时判断区间内点颜色是否一致
void add(int bit[],int x,int v)
{
while(x<=n) bit[x]+=v,x+=lowbit(x);
}
int sum(int bit[],int x)
{
int ret=0;
while(x>0) ret+=bit[x],x-=lowbit(x);
return ret;
}
int binary_search(int low,int up,int c)//low,up为新编号,low在下方,up在上方,二分查找某一重链上从哪个位置断开的
{
int mid,now,ret;
while(up<=low) {
mid=(low+up)>>1;
now=sum(T[2],low)-sum(T[2],mid-1);
if(c?now==0:now==low-mid+1) low=mid-1,ret=mid;
else up=mid+1;
}
return rnk[ret];//返回值为ret映射的旧编号
}
int find(int x)//找到从x向上能移动到的最浅位置,即所在同色块的顶端位置
{
int c=col[x],now;
while(top[x]!=1) {
now=sum(T[2],idx[x])-sum(T[2],idx[top[x]]-1);//计算重链的黑点个数
if(c?now==0:now==idx[x]-idx[top[x]]+1)//若该点为黑,且区间全黑;或者该点为白,且区间无黑
if(c==col[fa[top[x]]]) x=fa[top[x]];//若重链顶端父节点与x颜色一致,继续向上找
else return top[x];//否则能到达最浅位置即top[x]
else return binary_search(idx[x],idx[top[x]],c);//否则区间存在断点,二分查找断点
}
return binary_search(idx[x],idx[1],c);//x已在1所在重链上,二分查找断点
}
void update(int x,int y,int v,int c)//树上两点间bit更新
{
while(top[x]!=top[y]) {
if(dep[top[x]]1,-v);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
add(T[c],idx[x],v);
add(T[c],idx[y]+1,-v);
}
void solve(int x)//注意当前点不参与修改,find(x)的父节点也要修改,因为当该点也变换颜色时,是不会修改其本身的,所以在之前就应当先修改好
{
if(x>1) update(fa[find(x)],fa[x],-sum(T[col[x]],idx[x]),col[x]);//颜色变换前x上方能影响到的所有点进行修改
add(T[2],idx[x],(col[x]^=1)?-1:1);//修改T[2]中黑点个数
if(x>1) update(fa[find(x)],fa[x],sum(T[col[x]],idx[x]),col[x]);//颜色变换后x上方能影响到的所有点进行修改
}
int main()
{
n=read();
for(int u,v,i=1;i1,1);
dfs2(1,1,1);
for(int i=1;i<=n;i++) add(T[0],idx[i],sz[i]),add(T[0],idx[i]+1,-sz[i]),add(T[2],i,1);
add(T[1],1,1);//白树中,初始化成1
q=read();
while(q--) {
int op,x;
op=read(),x=read();
if(op) solve(x);
else printf("%d\n",sum(T[col[x]],idx[find(x)]));
}
return 0;
}