BZOJ4568 || 洛谷P3292 [SCOI2016]幸运数字【线性基+倍增/树剖】

6 篇文章 0 订阅
5 篇文章 0 订阅

Time Limit: 60 Sec
Memory Limit: 256 MB

Description

A 国共有 n 座城市,这些城市由 n-1 条道路相连,使得任意两座城市可以互达,且路径唯一。每座城市都有一个
幸运数字,以纪念碑的形式矗立在这座城市的正中心,作为城市的象征。一些旅行者希望游览 A 国。旅行者计划
乘飞机降落在 x 号城市,沿着 x 号城市到 y 号城市之间那条唯一的路径游览,最终从 y 城市起飞离开 A 国。
在经过每一座城市时,游览者就会有机会与这座城市的幸运数字拍照,从而将这份幸运保存到自己身上。然而,幸
运是不能简单叠加的,这一点游览者也十分清楚。他们迷信着幸运数字是以异或的方式保留在自己身上的。例如,
游览者拍了 3 张照片,幸运值分别是 5,7,11,那么最终保留在自己身上的幸运值就是 9(5 xor 7 xor 11)。
有些聪明的游览者发现,只要选择性地进行拍照,便能获得更大的幸运值。例如在上述三个幸运值中,只选择 5
和 11 ,可以保留的幸运值为 14 。现在,一些游览者找到了聪明的你,希望你帮他们计算出在他们的行程安排中
可以保留的最大幸运值是多少。

Input

第一行包含 2 个正整数 n ,q,分别表示城市的数量和旅行者数量。第二行包含 n 个非负整数,其中第 i 个整
数 Gi 表示 i 号城市的幸运值。随后 n-1 行,每行包含两个正整数 x ,y,表示 x 号城市和 y 号城市之间有一
条道路相连。随后 q 行,每行包含两个正整数 x ,y,表示这名旅行者的旅行计划是从 x 号城市到 y 号城市。N
<=20000,Q<=200000,Gi<=2^60

Output

输出需要包含 q 行,每行包含 1 个非负整数,表示这名旅行者可以保留的最大幸运值。


题目分析

时间限制看着就吓人

解法一:树上倍增

a[u][i] a [ u ] [ i ] 储存 u u u 2i 2 i 级祖先的线性基
同样也要用 fa[u][i] f a [ u ] [ i ] 记录u的祖先

那么我们可以在预处理fa数组的时候同时处理a数组

for(int i=1;(1<<i)<=n;++i)
for(int u=1;u<=n;++u)
{
    fa[u][i]=fa[fa[u][i-1]][i-1];
    a[u][i]=merge(a[u][i-1],a[fa[u][i-1]][i-1]);
}

注意到这里fa[u][i]的值似乎被合并了两次
但其实是没有关系的
由于已经插入过一次
如果再次插入 val[fa[u][i]] v a l [ f a [ u ] [ i ] ] 的值,他会在一系列操作后变成0而被忽略

与处理完后对于每个询问
找到LCA(u,v)
树上倍增合并u到lca和v到lca路径上的线性基
最后在得到的线性基中查询即可

int u=read(),v=read();
int lca=LCA(u,v); node tt;
tt.ins(val[u]);//因为有可能存在u==v,所以要先插入val[lca]
for(int i=15;i>=0;--i)
{
    if(dep[fa[u][i]]>=dep[lca])tt=merge(tt,a[u][i]),u=fa[u][i];
    if(dep[fa[v][i]]>=dep[lca])tt=merge(tt,a[v][i]),v=fa[v][i];
}
printf("%lld\n",qmax(tt));

完整代码
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;

lt read()
{
    lt f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=20010;
int n,m;
lt val[maxn];
struct edge{int v,nxt;}E[maxn<<1];
int head[maxn],tot;
int fa[maxn][20],dep[maxn];
struct node
{
    lt d[63];
    node(){memset(d,0,sizeof(d));}
    void ins(lt x)
    {
        for(lt i=62;i>=0;--i)
        if(x&((lt)(1)<<i))
        {
            if(!d[i]){d[i]=x;break;}
            else x^=d[i];
        }
    } 
}a[maxn][15];

void add(int u,int v)
{
    E[++tot].nxt=head[u];
    E[tot].v=v;
    head[u]=tot;
}

void dfs(int u,int pa)
{
    a[u][0].ins(val[u]);
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v==pa) continue;
        fa[v][0]=u; dep[v]=dep[u]+1;
        a[v][0].ins(val[u]);
        dfs(v,u);
    }
}

int LCA(int u,int v)
{
    if(dep[u]<dep[v])swap(u,v);
    int d=dep[u]-dep[v];

    for(int i=0;(1<<i)<=d;++i)
    if(d&(1<<i)) u=fa[u][i];
    if(u==v)return u;

    for(int i=(int)log(n);i>=0;--i)
    if(fa[u][i]!=fa[v][i])
    u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}

node merge(node u,node v)
{
    for(int i=62;i>=0;--i)
    if(v.d[i])u.ins(v.d[i]);
    return u;
}

lt qmax(node tt)
{
    lt ans=0;
    for(int i=62;i>=0;--i)
    if((ans^tt.d[i])>ans)ans^=tt.d[i];
    return ans;
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;++i)val[i]=read();
    for(int i=1;i<n;++i)
    {
        int u=read(),v=read();
        add(u,v);add(v,u);
    }

    dep[1]=1; dfs(1,0);
    for(int i=1;(1<<i)<=n;++i)
    for(int u=1;u<=n;++u)
    {
        fa[u][i]=fa[fa[u][i-1]][i-1];
        a[u][i]=merge(a[u][i-1],a[fa[u][i-1]][i-1]);
    }

    while(m--)
    {
        int u=read(),v=read();
        int lca=LCA(u,v); node tt;
        tt.ins(val[u]);
        for(int i=15;i>=0;--i)
        {
            if(dep[fa[u][i]]>=dep[lca])tt=merge(tt,a[u][i]),u=fa[u][i];
            if(dep[fa[v][i]]>=dep[lca])tt=merge(tt,a[v][i]),v=fa[v][i];
        }
        printf("%lld\n",qmax(tt));
    }
    return 0;
}


解法二:树链剖分

树剖后线段树上每个节点保存其对应区间的线性基
对每个询问只要在线段树上查找对应区间合并
最后在答案线性基中查找就好

理论上时间复杂度应该和倍增差不多
但是luogu上还是要开O2才过最后一个点
可能是我脸黑(?)


完整代码
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;

lt read()
{
    lt f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

void print(lt x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9) print(x/10);
    putchar(x%10+'0');
}

const int maxn=20010;
int n,m;
lt val[maxn];
struct edge{int v,nxt;}E[maxn<<1];
int head[maxn],tot;
int fa[maxn],dep[maxn],son[maxn],cnt;
int size[maxn],top[maxn],num[maxn],pos[maxn];
lt a[maxn<<2][62],ans[65],tp[65];

void add(int u,int v)
{
    E[++tot].nxt=head[u];
    E[tot].v=v;
    head[u]=tot;
}

void ins(lt *tt,lt x)
{
    for(lt i=60;i>=0;--i)
    if(x&((lt)(1)<<i))
    {
        if(!tt[i]){tt[i]=x;break;}
        else x^=tt[i];
    }
} 

void merge(lt *a,lt *b)
{
    for(int i=60;i>=0;--i)
    if(b[i])ins(a,b[i]);
}

lt qmax(lt *tt)
{
    lt ans=0;
    for(int i=60;i>=0;--i)
    if((ans^tt[i])>ans)ans^=tt[i];
    return ans;
}

void dfs1(int u,int pa)
{
    size[u]=1;
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v==pa) continue;
        fa[v]=u; dep[v]=dep[u]+1;
        dfs1(v,u);
        size[u]+=size[v];
        if(size[v]>size[son[u]])son[u]=v;
    }
}

void dfs2(int u,int tp)
{
    top[u]=tp; num[u]=++cnt; pos[cnt]=u;
    if(son[u])dfs2(son[u],tp);
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v==fa[u]||v==son[u])continue;
        dfs2(v,v);
    }
}

void build(int s,int t,int p)
{
    if(s==t){ins(a[p],val[pos[s]]);return;}
    int mid=s+t>>1,lc=p<<1,rc=p<<1|1;
    build(s,mid,lc);build(mid+1,t,rc);
    merge(a[p],a[lc]); merge(a[p],a[rc]);
}

void get(int ll,int rr,int s,int t,int p)
{
    if(ll<=s&&t<=rr)return merge(tp,a[p]);
    int mid=s+t>>1;
    if(ll<=mid) get(ll,rr,s,mid,p<<1);
    if(rr>mid) get(ll,rr,mid+1,t,p<<1|1);
}

lt solve(int u,int v)
{
    memset(ans,0,sizeof(ans));
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        memset(tp,0,sizeof(tp));
        get(num[top[u]],num[u],1,n,1);
        merge(ans,tp);
        u=fa[top[u]];
    }
    memset(tp,0,sizeof(tp));
    if(dep[v]<dep[u])swap(u,v);
    get(num[u],num[v],1,n,1);
    merge(ans,tp);
    return qmax(ans);
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;++i)val[i]=read();
    for(int i=1;i<n;++i)
    {
        int u=read(),v=read();
        add(u,v);add(v,u);
    }

    dep[1]=1;
    dfs1(1,0); dfs2(1,1);
    build(1,n,1);

    while(m--)
    {
        int u=read(),v=read();
        print(solve(u,v)); putchar('\n');
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值