1. 程式人生 > >BZOJ5361[Lydsy1805月賽]對稱數——主席樹

BZOJ5361[Lydsy1805月賽]對稱數——主席樹

sizeof amp cst turn href upd root 判斷 namespace

題目鏈接: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));
        }
    }
}

BZOJ5361[Lydsy1805月賽]對稱數——主席樹