【BZOJ 3757】 苹果树

3757: 苹果树

Time Limit: 20 Sec  Memory Limit: 256 MB
Submit: 360  Solved: 111
[ Submit][ Status]

Description

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

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

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

Input

输入第一行为两个整数n和m,分别代表树上苹果的个数和前来膜拜的人数。
接下来的一行包含n个数,第i个数代表编号为i的苹果的颜色Coli。
接下来有n行,每行包含两个数x和y,代表有一根树枝连接了苹果x和y(或者根和一个苹果)。
接下来有m行,每行包含四个整数u、v、a和b,代表这个人要数苹果u到苹果v的颜色种数,同时这个人认为颜色a就是颜色b。如果a=b=0,则代表这个人没有患色盲症。

Output

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

Sample Input

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

Sample Output

2
1
2

HINT

0<=x,y,a,b<=N

N<=50000

1<=U,V,Coli<=N


树上莫队。


对于序列的莫队是先把序列分块,再按照左端点处在的块数作为第一关键字,右端点作为第二关键字对询问排序,再进行转移就可以了。


对于树的同理。做了【bzoj1086】树分块就明白了,按照同样的方法对询问排序,那么关键就在于如何转移了:


vfk的做法:

用S(v, u)代表 v到u的路径上的结点的集合。
用root来代表根结点,用lca(v, u)来代表v、u的最近公共祖先。
那么
S(v, u) = S(root, v) xor S(root, u) xor lca(v, u)
其中xor是集合的对称差。
简单来说就是 节点出现两次消掉。

lca很讨厌,于是再定义
T(v, u) = S(root, v) xor S(root, u)
观察将curV移动到targetV前后T(curV, curU)变化:
T(curV, curU) = S(root, curV) xor S(root, curU)
T(targetV, curU) = S(root, targetV) xor S(root, curU)
取对称差:
T(curV, curU) xor T(targetV, curU)= (S(root, curV) xor S(root, curU)) xor (S(root, targetV) xor S(root, curU))
由于对称差的交换律、结合律:
T(curV, curU) xor T(targetV, curU)= S(root, curV) xor S(root, targetV)
两边同时xor T(curV, curU):
T(targetV, curU)= T(curV, curU) xor S(root, curV) xor S(root, targetV)
发现最后两项很爽……哇哈哈
T(targetV, curU)= T(curV, curU) xor T(curV, targetV)
(有公式恐惧症的不要走啊 T_T)

也就是说,更新的时候,xor  T (curV,  targetV)就行了。
即,对curV到targetV路径(除开lca(curV, targetV))上的结点,将它们的存在性取反即可。

因此从(curV,curU)转移到(targetV,targetU)  【注意这些状态都不包含lca】 就是对(curV,targetV)上的点取反(对lca不进

行操作),再对(curU,targetU)上的点取反(对lca不进行操作),就转移过来了,然后单独对lca取反,把答案保存后,

再把lca取反,因此仍是相当于对lca没有操作。


对于色盲的问题,只要在算出答案之后判断一下就可以。


#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#define  N 50005
using namespace std;
int dfn[N],Ans[N*3],n,m,top=0,c[N],h[N],tot=0,cnt=0,be[N],dep[N],b[20];
int a,dfs_clock=0,f[N][17],k,ans=0,s[N],v[N],num[N];
int read()
{
    int tmp=0,fu=1;
    char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar())
        if (ch=='-') fu=-1;
    for (;ch>='0'&&ch<='9';ch=getchar())
        tmp=tmp*10+ch-'0';
    return tmp;
}
struct Query
{
    int x,y,a,b,id;
}q[N*3];
struct edge
{
    int y,ne;
}e[N*3];
void Addedge(int x,int y)
{
    e[++tot].y=y;
    e[tot].ne=h[x];
    h[x]=tot;
}
void dfs(int x)
{
    dfn[x]=++dfs_clock;
    int bottom=top;
    for (int i=h[x];i;i=e[i].ne)
        if (e[i].y!=f[x][0])
        {
            int y=e[i].y;
            dep[y]=dep[x]+1;
            f[y][0]=x;
            dfs(y);
            if (top-bottom>=k)
            {
                cnt++;
                while (top!=bottom)
                    be[s[top--]]=cnt;
            }
        }
    s[++top]=x;
}
bool cmp(Query a,Query b)
{
    if (be[a.x]==be[b.x]) return dfn[a.y]<dfn[b.y];
    return be[a.x]<be[b.x];
}
void Prepare()
{
    b[0]=1;
    for (int i=1;i<=16;i++)
        b[i]=b[i-1]<<1;
    for (int i=1;i<=16;i++)
        for (int j=1;j<=n;j++)
            f[j][i]=f[f[j][i-1]][i-1];
}
void Move(int &x,int deep)
{
    for (int i=16;i>=0;i--)
        if (dep[f[x][i]]>=deep)
            x=f[x][i];
}
int Getlca(int x,int y)
{
    if (dep[x]>dep[y]) swap(x,y);
    Move(y,dep[x]);
    if (x==y) return x;
    for (int i=16;i>=0;i--)
        if (f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
    return f[x][0];
}
void Calc(int x)
{
    if (!v[x])
    {
        v[x]=1;
        num[c[x]]++;
        if (num[c[x]]==1) ans++;
    }
    else
    {
        v[x]=0;
        num[c[x]]--;
        if (!num[c[x]]) ans--;
    }
}
void Solve(int x,int y)
{
    while (x!=y)
    {
        if (dep[x]<dep[y])
            swap(x,y);
        Calc(x);
        x=f[x][0];
    }
}
int main()
{
    n=read(),m=read();
    for (int i=1;i<=n;i++)
        c[i]=read();
    int root;
    for (int i=1;i<=n;i++)
    {
        int x,y;
        x=read(),y=read();
        if (x==0) root=y;
        else if (y==0) root=x;
        else Addedge(x,y),Addedge(y,x);
    }
    k=sqrt(n);
    dep[root]=1;
    dfs(root);
    cnt++;
    while (top)
        be[s[top--]]=cnt;
    for (int i=1;i<=m;i++)
    {
        q[i].x=read(),q[i].y=read(),q[i].a=read(),q[i].b=read();
        if (dfn[q[i].x]>dfn[q[i].y])
			swap(q[i].x,q[i].y);
        q[i].id=i;
    }
    
    sort(q+1,q+1+m,cmp);
    Prepare();
	q[0].x=q[0].y=1;
    for (int i=1;i<=m;i++)
    {
        a=Getlca(q[i].x,q[i].y);
        Solve(q[i-1].x,q[i].x);
        Solve(q[i-1].y,q[i].y);
        Calc(a);
        Ans[q[i].id]=ans;
        if (q[i].a!=q[i].b&&num[q[i].a]&&num[q[i].b])
            Ans[q[i].id]--;
        Calc(a);
    }
    for (int i=1;i<=m;i++)
        printf("%d\n",Ans[i]);
    return 0;
}



感悟:

1.tle是因为lca求错了。。


你可能感兴趣的:(OI,bzoj,树上莫队)