[SPOJ16549 QTREE6 - Query on a tree VI]

洛谷传送门
BZOJ传送门

题目描述(翻译)

给你一棵n个点的树,编号 1 1 n。每个点可以是黑色,可以是白色。初始时所有点都是黑色。下面有两种操作请你操作给我们看:
0 u 0   u :询问有多少个节点 v v 满足路径u v v 上所有节点(包括)都拥有相同的颜色
1 u:翻转u的颜色

输入输出格式

输入格式

第一行一个整数 n n
接下来n1行,每行两个整数表示一条边
接下来一行一个整数 m 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);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值