BZOJ5361[Lydsy1805月赛]对称数——主席树+随机化

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=5361

 

好神的一道题啊!

容易看出来是要用维护权值的数据结构,因此树链剖分首先pass掉。

那么不妨用树上主席树试试?每个版本存当前点到根路径上的点的权值。

如果维护区间权值数量的话,你发现没有明确的判断条件来明确每一次主席树上二分是走左子树还是右子树。

这时就要用到一个套路了:将1~200000的所有权值随机映射成unsigned long long的数,主席树维护区间权值异或和。

再维护前缀权值异或和,这样每次在主席树上二分时只要判断左子树的权值异或和是否等于左子树代表的区间的权值异或和。

如果等于,就说明左子树所有权值都出现了奇数次,答案一定在右子树中。反之则在左子树中。

因为是随机化算法,所以只能保证大概率的正确性,不过这种套路在做题时也可以适当借鉴。

最后注意主席树区间要开到200001,因为可能前200000个数都有,答案是200001。

#include<set>
#include<map>
#include<stack>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ull unsigned long long
using namespace std;
inline char _read()
{
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
    int x=0,f=1;char ch=_read();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=_read();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=_read();}
    return x*f;
}
int T;
int x,y;
int n,m;
int cnt;
int tot;
int a[200010];
int d[200010];
ull v[200010];
int to[400010];
int ls[8000010];
int rs[8000010];
ull num[200010];
int root[200010];
int head[200010];
int next[400010];
ull sum[8000010];
int f[200010][19];
ull Rand()
{
    return ((ull)rand()<<45)|((ull)rand()<<30)|(rand()<<15)|rand();
}
void add(int x,int y)
{
    tot++;
    next[tot]=head[x];
    head[x]=tot;
    to[tot]=y;
}
void updata(int &rt,int pre,int l,int r,int k)
{
    rt=++cnt;
    if(l==r)
    {
        sum[rt]=sum[pre]^v[l];
        return ;
    }
    ls[rt]=ls[pre];
    rs[rt]=rs[pre];
    int mid=(l+r)>>1;
    if(k<=mid)
    {
        updata(ls[rt],ls[pre],l,mid,k);
    }
    else
    {
        updata(rs[rt],rs[pre],mid+1,r,k);
    }
    sum[rt]=sum[ls[rt]]^sum[rs[rt]];
}
void dfs(int x)
{
    d[x]=d[f[x][0]]+1;
    updata(root[x],root[f[x][0]],1,200001,a[x]);
    for(int i=1;i<=18;i++)
    {
        f[x][i]=f[f[x][i-1]][i-1];
    }
    for(int i=head[x];i;i=next[i])
    {
        if(to[i]!=f[x][0])
        {
            f[to[i]][0]=x;
            dfs(to[i]);
        }
    }
}
int lca(int x,int y)
{
    if(d[x]<d[y])
    {
        swap(x,y);
    }
    int dep=d[x]-d[y];
    for(int i=0;i<=18;i++)
    {
        if((dep&(1<<i))!=0)
        {
            x=f[x][i];
        }
    }
    if(x==y)
    {
        return x;
    }
    for(int i=18;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
int query(int x,int y,int fa,int anc,int l,int r)
{
    if(l==r)
    {
        return l;
    }
    ull res=sum[ls[x]]^sum[ls[y]]^sum[ls[fa]]^sum[ls[anc]];
    int mid=(l+r)>>1;
    if(res==(num[mid]^num[l-1]))
    {
        return query(rs[x],rs[y],rs[fa],rs[anc],mid+1,r);
    }
    else
    {
        return query(ls[x],ls[y],ls[fa],ls[anc],l,mid);
    }
}
int main()
{
    srand(20020419);
    T=read();
    for(int i=1;i<=200001;i++)
    {
        v[i]=Rand();
        num[i]=num[i-1]^v[i];
    }
    while(T--)
    {
        cnt=0;
        tot=0;
        memset(d,0,sizeof(d));
        memset(f,0,sizeof(f));
        memset(ls,0,sizeof(ls));
        memset(rs,0,sizeof(rs));
        memset(sum,0,sizeof(sum));
        memset(root,0,sizeof(root));
        memset(head,0,sizeof(head));
        n=read();
        m=read();
        for(int i=1;i<=n;i++)
        {
            a[i]=read();
        }
        for(int i=1;i<n;i++)
        {
            x=read();
            y=read();
            add(x,y);
            add(y,x);
        }
        dfs(1);
        while(m--)
        {
            x=read();
            y=read();
            int anc=lca(x,y);
            printf("%d\n",query(root[x],root[y],root[anc],root[f[anc][0]],1,200001));
        }
    }
}

转载于:https://www.cnblogs.com/Khada-Jhin/p/9687803.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值