[BZOJ 3757] 苹果树

BZOJ传送门

题目描述

神犇家门口种了一棵苹果树。苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条。由于这棵苹果树是神犇种的,所以苹果都发生了变异,变成了各种各样的颜色。我们用一个到 n n 之间的正整数来表示一种颜色。树上一共有n个苹果。每个苹果都被编了号码,号码为一个 1 1 n之间的正整数。我们用 0 0 代表树根。只会有一个苹果直接根。

有许许多多的人来神犇家里膜拜神犇。可神犇可不是随便就能膜拜的。前来膜拜神犇的人需要正确回答一个问题,才能进屋膜拜神犇。这个问题就是,从树上编号为u的苹果出发,由树枝走到编号为v的苹果,路径上经过的苹果一共有多少种不同的颜色(包括苹果 u u 和苹果v的颜色)?不过神犇注意到,有些来膜拜的人患有色盲症。具体地说,一个人可能会认为颜色 a a 就是颜色b,那么他们在数苹果的颜色时,如果既出现了颜色 a a 的苹果,又出现了颜色b的苹果,这个人只会算入颜色 b b ,而不会把颜色a算进来。

神犇是一个好人,他不会强人所难,也就会接受由于色盲症导致的答案错误(当然答案在色盲环境下也必须是正确的)。不过这样神犇也就要更改他原先数颜色的程序了。虽然这对于神犇来说是小菜一碟,但是他想考验一下你。你能替神犇完成这项任务吗?

输入输出格式

输入格式

输入第一行为两个整数 n n m,分别代表树上苹果的个数和前来膜拜的人数。

接下来的一行包含 n n 个数,第i个数代表编号为i的苹果的颜色Coli

接下来有 n n 行,每行包含两个数x y y ,代表有一根树枝连接了苹果x y y (或者根和一个苹果)。

接下来有m行,每行包含四个整数 uva u 、 v 、 a b b ,代表这个人要数苹果u到苹果 v v 的颜色种数,同时这个人认为颜色a就是颜色 b b 。如果a=b=0,则代表这个人没有患色盲症。

输出格式

输出一共 m m 行,每行仅包含一个整数,代表这个人应该数出的颜色种数。

输入样例

5 3
1 1 3 3 2
0 1
1 2
1 3
2 4
3 5
1 4 0 0
1 4 1 3
1 4 1 2

输出样例

2
1
2

数据范围

0x,y,a,bN
N50000 N ≤ 50000
1U,V,ColiN 1 ≤ U , V , C o l i ≤ N
M100000 M ≤ 100000

解题分析

蒟蒻A的树上莫队的第一道题…
考虑按王室联邦的方法对树进行分块, 这样分块后树上莫队就变成了序列莫队。那么如何从 (x1,y1) ( x 1 , y 1 ) 转移至 (x2,y2) ( x 2 , y 2 ) ? 画图后不难发现只需要将 x1 x 1 x2 x 2 y1 y 1 y2 y 2 路径上面的状态取反即可。 不过因为 lca l c a 会被讨论两次, 所以不如单独计算。

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 100010
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;
}
int head[MX], col[MX], ans[MX], seg[MX], st[MX], fat[MX], topf[MX], son[MX], siz[MX], cot[MX], dep[MX];
int block, q, dot, top, arr, res, cnt;
bool ex[MX];
struct Edge
{
    int to, nex;
}edge[MX];
IN void addedge(const int &from, const int &to)
{
    edge[++cnt] = {to, head[from]};
    head[from] = cnt;
}
struct Que
{
    int lef, rig, x, y, id;
}eve[MX];
IN bool operator < (const Que &x, const Que &y)
{//同样是奇偶优化
    if(seg[x.lef] != seg[y.lef]) return seg[x.lef] < seg[y.lef];
    return (seg[x.lef] & 1) ? seg[x.rig] < seg[y.rig] : seg[x.rig] > seg[y.rig];
}
void DFS(const int &now)//树剖LCA和分块一起写
{
    int bot = top; siz[now] = 1;
    for (int i = head[now]; i; i = edge[i].nex)
    {
        if(edge[i].to == fat[now]) continue;
        dep[edge[i].to] = dep[now] + 1;
        fat[edge[i].to] = now;
        DFS(edge[i].to);
        siz[now] += siz[edge[i].to];
        if(siz[edge[i].to] > siz[son[now]]) son[now] = edge[i].to;
        if(top - bot >= block)
        {
            ++arr;
            W (top > bot) seg[st[top--]] = arr;
        }
    }
    st[++top] = now;
}
void DFS(const int &now, const int &grand)//树剖的另一个DFS
{
    topf[now] = grand;
    if(son[now] == 100001) return;
    DFS(son[now], grand);
    for (R int i = head[now]; i; i = edge[i].nex)
    {
        if(edge[i].to == fat[now] || edge[i].to == son[now]) continue;
        DFS(edge[i].to, edge[i].to);
    }
}
IN int LCA(R int x, R int y)
{
    W (topf[x] != topf[y])
    {
        if(dep[topf[x]] < dep[topf[y]]) std::swap(x, y);
        x = fat[topf[x]];
    }
    return dep[x] > dep[y] ? y : x;
}
IN void modify(const int &now)
{
    if(!ex[now]) {++cot[col[now]]; if(cot[col[now]] == 1) ++res;}
    else {--cot[col[now]]; if(!cot[col[now]]) --res;}
    ex[now] ^= 1;
}
IN void cal(R int x, R int y)
{
    W (x ^ y)//暴力往上跳即可
    {
        if(dep[x] > dep[y]) modify(x), x = fat[x];
        else modify(y), y = fat[y];
    }
}
int main(void)
{
    std::fill(son, son + 100005, 100001);//有0点, 所以赋为一个不会重的位置
    int a, b, lca, lb, rb;
    in(dot), in(q); block = sqrt(dot) + 1;
    for (R int i = 1; i <= dot; ++i) in(col[i]);
    for (R int i = 1; i <= dot; ++i)
    in(a), in(b), addedge(a, b), addedge(b, a);
    dep[0] = 1;
    DFS(0);
    W (top) seg[top--] = arr;
    DFS(0, 0);
    for (R int i = 1; i <= q; ++i)
    {
        in(eve[i].lef), in(eve[i].rig), in(eve[i].x), in(eve[i].y); eve[i].id = i;
        if(seg[eve[i].lef] > seg[eve[i].rig]) std::swap(eve[i].lef, eve[i].rig);
    }
    std::sort(eve + 1, eve + 1 + q); lca = LCA(eve[1].lef, eve[1].rig);
    cal(eve[1].lef, eve[1].rig); modify(lca);//单独处理LCA
    ans[eve[1].id] = res;
    if(eve[1].x != eve[1].y && cot[eve[1].x] && cot[eve[1].y]) --ans[eve[1].id];
    modify(lca), lb = eve[1].lef, rb = eve[1].rig;
    for (R int i = 2; i <= q; ++i)
    {
        cal(lb, eve[i].lef), cal(rb, eve[i].rig);
        lca = LCA(eve[i].lef, eve[i].rig);
        modify(lca);
        ans[eve[i].id] = res;
        if(eve[i].x != eve[i].y && cot[eve[i].x] && cot[eve[i].y]) --ans[eve[i].id];
        modify(lca); lb = eve[i].lef, rb = eve[i].rig;
    }
    for (R int i = 1; i <= q; ++i) printf("%d\n", ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值