JZOJ4632. 【SCOI2016】幸运数字

题目大意

给一棵含有 N 个结点的树,每个点有一个权值Gi。给 Q 个询问(u,v),求 u ->v路径上,选出一些点,使得他们权值的异或值最大,求最大异或值。

Data Constraint
N20000
Q200000
Gi<260

题解

考虑一个叫线性基的东西。

线性基

线性基是一个主要解决有关异或方面的问题。它的主要思想就是用尽可能少的数表示出区间中的所有数。根据定义我们就可以得出线性基的几个性质:

  • 对于区间的每一个数都可以由线性基中的是异或得来。
  • 线性基中的每个数都不可以由线性基中的其他数异或而来。

如何合并/构造线性基?

合并两个线性基时(构造线性基也类似)只需把其中一个线性基的数暴力加到另一个中就可以了。
加入时我们首先需要判断的就是当前这个数能否被另一个线性基中的数表示。
假如当前待加入的这个数的第i位为1,那么我们就要判断另一个线性基中的第i位有没有存下数字。如果有那么这一位就可以被消去(可以被其他线性基表示),如果没有那么这个数就不能被另一个线性基表示,所以在第i位加入这个数。所以合并两个线性基的复杂度是 O(log22(maxAi)) (即最大的数二进制位数平方)的。

参考代码

//f[i][j][k]表示以点i开始往上跳2^j个点的范围内第k为是1的线性基是哪个数
void ADD( int x , ll v ) {
    for (int i = 60 ; i >= 0 ; i -- ) {
        if ( !((v >> i) & 1) ) continue ;
        if ( f[x][0][i] == 0 ) { f[x][0][i] = v ; break ; }
        v ^= f[x][0][i] ;
    }
}

得到了线性基之后,我们可以维护以点 i 开始往上跳2j个点的范围内线性基是什么,不妨记为 f[i][j] 。预处理出 f <script type="math/tex" id="MathJax-Element-791">f</script>之后就可以类似RMQ那样合并。

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std ;

#define N 20000 + 5
#define M 60 + 5
typedef long long ll ;
const int MAXN = 16 ;

ll f[N][MAXN][M] , G[N] , tp[M] ;
int Node[2*N] , Next[2*N] , Head[N] , tot ;
int fa[N][MAXN] , Dep[N] , Tab[N] ;
int n , Qm , Cnt , ret ;
ll ans ;

void link( int u , int v ) {
    Node[++tot] = v ;
    Next[tot] = Head[u] ;
    Head[u] = tot ;
}

void ADD( int x , ll v ) {
    for (int i = 60 ; i >= 0 ; i -- ) {
        if ( !((v >> i) & 1) ) continue ;
        if ( f[x][0][i] == 0 ) { f[x][0][i] = v ; break ; }
        v ^= f[x][0][i] ;
    }
}

void DFS( int x ) {
    ADD( x , G[x] ) ;
    for (int p = Head[x] ; p ; p = Next[p] ) {
        if ( Node[p] == fa[x][0] ) continue ;
        fa[Node[p]][0] = x ;
        Dep[Node[p]] = Dep[x] + 1 ;
        DFS( Node[p] ) ;
    }
}

void Merge( ll *a , ll *b ) {
    for (int i = 0 ; i <= 60 ; i ++ ) {
        ll v = b[i] ;
        if ( v == 0 ) continue ;
        for (int j = 60 ; j >= 0 ; j -- ) {
            if ( ((v >> j) & 1) == 0 ) continue ; 
            if ( a[j] == 0 ) { a[j] = v ; break ; }
            v ^= a[j] ;
        }
    }
}

void Pre_LCA() {
    for (int j = 1 ; j < MAXN ; j ++ ) {
        for (int i = 1 ; i <= n ; i ++ ) {
            fa[i][j] = fa[fa[i][j-1]][j-1] ;
            memcpy( f[i][j] , f[i][j-1] , sizeof(f[i][j-1]) ) ;
            Merge( f[i][j] , f[fa[i][j-1]][j-1] ) ;
        }
    }
}

int LCA( int x , int y ) {
    if ( Dep[x] < Dep[y] ) swap( x , y ) ;
    for (int i = MAXN - 1 ; i >= 0 ; i -- ) {
        if ( Dep[fa[x][i]] >= Dep[y] ) x = fa[x][i] ;
    }
    if ( x == y ) return x ;
    for (int i = MAXN - 1 ; i >= 0 ; i -- ) {
        if ( fa[x][i] != fa[y][i] ) x = fa[x][i] , y = fa[y][i] ;
    }
    return fa[x][0] ;
}

int Goto( int x , int l ) {
    for (int i = MAXN - 1 ; i >= 0 ; i -- ) {
        if ( l >= (1 << i) ) {
            x = fa[x][i] ;
            l -= (1 << i) ;
        }
    }
    return x ;
}

void Solve( int u , int v ) {
    int lca = LCA( u , v ) ;
    int l1 = Dep[u] - Dep[lca] + 1 ;
    int k = Tab[l1] ;
    memcpy( tp , f[u][k] , sizeof(f[u][k]) ) ;
    u = Goto( u , l1 - (1 << k) ) ;
    Merge( tp , f[u][k] ) ;
    int l2 = Dep[v] - Dep[lca] + 1 ;
    k = Tab[l2] ;
    Merge( tp , f[v][k] ) ;
    v = Goto( v , l2 - (1 << k) ) ;
    Merge( tp , f[v][k] ) ;
    for (int i = 60 ; i >= 0 ; i -- ) ans = max( ans , ans ^ tp[i] ) ;
}

int main() {
    freopen( "lucky.in" , "r" , stdin ) ;
    freopen( "lucky.out" , "w" , stdout ) ;
    scanf( "%d%d" , &n , &Qm ) ;
    Tab[1] = 0 ;
    for (int i = 2 ; i <= n ; i ++ ) Tab[i] = Tab[i/2] + 1 ;
    for (int i = 1 ; i <= n ; i ++ ) scanf( "%lld" , &G[i] ) ;
    for (int i = 1 ; i < n ; i ++ ) {
        int u , v ;
        scanf( "%d%d" , &u , &v ) ;
        link( u , v ) ;
        link( v , u ) ;
    }
    Dep[1] = 1 ;
    DFS( 1 ) ;
    Pre_LCA() ;
    for (int i = 1 ; i <= Qm ; i ++ ) {
        int u , v ;
        scanf( "%d%d" , &u , &v ) ;
        ans = 0 ;
        Solve( u , v ) ;
        printf( "%lld\n" , ans ) ;
    }
    return 0 ;
}

以上.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值