洛谷传送门
BZOJ传送门
题目描述(翻译)
给你一棵n个点的树,编号
1
1
至。每个点可以是黑色,可以是白色。初始时所有点都是黑色。下面有两种操作请你操作给我们看:
0 u
0
u
:询问有多少个节点
v
v
满足路径到
v
v
上所有节点(包括)都拥有相同的颜色
:翻转u的颜色
输入输出格式
输入格式
第一行一个整数
n
n
接下来行,每行两个整数表示一条边
接下来一行一个整数
m
m
表示操作次数
接下来行,每行两个整数分别表示操作类型和被操作节点
输出格式
对每个询问操作输出相应的结果
输入输出样例
输入样例
5
1 2
1 3
1 4
1 5
3
0 1
1 1
0 1
输出样例
5
1
解题分析
这是一道妙妙的
LCT
L
C
T
题, 不过树剖似乎也可以做, 但如果不会LCT维护子树信息的朋友可以看我的这篇博客。
首先我们有个暴力的方法:建立黑白两棵树, 修改操作的时候暴力枚举与当前点相连的点进行
link
l
i
n
k
/
cut
c
u
t
。
不过样例已经告诉我们:要是菊花树就会死的很惨233……
所以在这里有一个妙妙的优化:化点为边。
平时做树上统计的题的时候如果遇到边权, 我们往往是在两个虚点之间建立一个实点表示边, 其权值为边的权值,方便维护链信息。但这里恰好非常特殊: 我们可以使用利用边的特性—-控制连通性来方便地操作。
具体来说, 我们将所有的点权(1)保存在与父节点相连的边中(当然只是个比喻, 并没有拆边, 而是通过连通性控制贡献), 每次
link
l
i
n
k
或
cut
c
u
t
时只操作当前点和其父节点的边。 这样的话每次我们将当前子树最上的有效点的父节点多算进去, 所以输出答案时要输出当前子树根节点的右儿子的信息。
还有一些细节, 见代码注释:
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cctype>
#define R register
#define IN inline
#define dad tree[typ][now].fat
#define ls tree[typ][now].son[0]
#define rs tree[typ][now].son[1]
#define gc getchar()
#define W while
#define MX 200005
/*
#define 0 = black
#define 1 = white
*/
template <class T>
IN void in (T &x)
{
x = 0; R char c = gc;
W (!isdigit(c)) c = gc;
W (isdigit(c))
x = (x << 1) + (x << 3) + c - 48, c = gc;
}
namespace LCT
{
int fat[MX], col[MX], head[MX], cnt, dot, q;
struct Edge
{
int to, nex;
}edge[MX << 1];
IN void addedge(const int &from, const int &to)
{edge[++cnt] = {to, head[from]}; head[from] = cnt;}
struct Node
{
int son[2], fat, siz, vir;//vir 表示虚儿子的子树大小
//因为是有根树, 儿子父亲关系已确定, 所以不能makeroot或split,所以不需要rev标记, 也就没有pushdown
}tree[2][MX];
IN void pushup(const int &typ, const int &now)
{tree[typ][now].siz = tree[typ][now].vir + 1 + tree[typ][ls].siz + tree[typ][rs].siz;}
IN bool get(const int &typ, const int &now) {return tree[typ][dad].son[1] == now;}
IN bool nroot(const int &typ, const int &now) {return tree[typ][dad].son[1] == now || tree[typ][dad].son[0] == now;}
IN void rotate(const int &typ, const int &now)
{
R bool dir = get(typ, now);
R int fa = dad, grand = tree[typ][fa].fat;
tree[typ][fa].son[dir] = tree[typ][now].son[dir ^ 1];
tree[typ][tree[typ][now].son[dir ^ 1]].fat = fa;
if(nroot(typ, fa)) tree[typ][grand].son[get(typ, fa)] = now;
tree[typ][now].fat = grand;
tree[typ][now].son[dir ^ 1] = fa;
tree[typ][fa].fat = now;
pushup(typ, fa);
}
IN void splay(const int &typ, R int now)
{
R int fa, grand;
W (nroot(typ, now))
{
fa = dad, grand = tree[typ][fa].fat;
if(nroot(typ, fa)) rotate(typ, (get(typ, now) == get(typ, fa) ? fa : now));
rotate(typ, now);
}
pushup(typ, now);
}
IN void access(const int &typ, R int now)
{
for (R int x = 0; now; x = now, now = dad)
{
splay(typ, now);
tree[typ][now].vir += tree[typ][rs].siz;
tree[typ][now].vir -= tree[typ][x].siz;
rs = x;
}
}
IN int findroot(const int &typ, R int now)
{
access(typ, now), splay(typ, now);
W (ls) now = ls;
splay(typ, now);//这里很特殊的是findroot后需要将root转回去得到其右儿子
return now;
}
IN void link(const int &typ, R int now)
{
access(typ, now), splay(typ, now);
R int fa = dad = fat[now];
access(typ, fa), splay(typ, fa);//同上, 为了保证信息正确性, 不改变其在树上位置
tree[typ][fa].siz += tree[typ][now].siz;
tree[typ][fa].vir += tree[typ][now].siz;
}
IN void cut(const int &typ, const int &now)
{
access(typ, now), splay(typ, now);
ls = tree[typ][ls].fat = 0;
pushup(typ, now);
}
void DFS(const int &now)//一遍DFS建树
{
for (int i = head[now]; i; i = edge[i].nex)
{
if(edge[i].to == fat[now]) continue;
fat[edge[i].to] = now;
DFS(edge[i].to);
link(0, edge[i].to);
}
}
}
using namespace LCT;
int main(void)
{
int a, b, root;
in(dot);
for (R int i = 1; i <= dot; ++i) tree[0][i].siz = tree[1][i].siz = 1;//初始化不要忘记
for (R int i = 1; i < dot; ++i)
{
in(a), in(b);
addedge(a, b), addedge(b, a);
}
DFS(1);
fat[1] = dot + 1; link(0, 1);//为了保证我们建立的模型的正确性, 我们把0节点空出来, 建一个虚点使之成为1号点的父亲节点
in(q);
W (q--)
{
in(a), in(b);
if(a) cut(col[b], b), link(col[b] ^= 1, b);
else
{
root = findroot(col[b], b);
printf("%d\n", tree[col[b]][tree[col[b]][root].son[1]].siz);
}
}
}