《洛谷P3292 [SCOI2016]幸运数字》

这道题真的让我受益匪浅,加深了对线性基的认识。

首先,考虑了一般解法,较暴力。

将x->y路径上所有的点都加入一个空的线性基集合,最后求一次最大值即可。

既然要将每个点都加入,那么必然要一个点一个点地跳。(因为每个点都要遍历到,所以不能倍增)

那么,这时最坏复杂度在N^2。只有30分。

考虑去优化。

思路1:首先,对于线性基,它是可以合并的,即将线性基A里的每个数都插入到线性基B中。

所以我们只需要做到的就是高效地合并,高效地查询。

因为是树上的合并,树剖登场。

对于线段树上的点,我们去维护它的子节点合并后的线性基。

那么我们树剖查询时,将一整条链的线性基都合并到答案中。

每次都能logn处理出一条链的点。那么复杂度就降到了nlogn^4左右。

然后就可以过了。

// Author: levil
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
const int N = 2e4+5;
const int M = 1e4;
const LL Mod = 1e9+7;
#define rg register
#define pi acos(-1)
#define INF 1e18
#define CT0 cin.tie(0),cout.tie(0)
#define IO ios::sync_with_stdio(false)
#define dbg(ax) cout << "now this num is " << ax << endl;
namespace FASTIO{
    inline LL read(){
        LL x = 0,f = 1;char c = getchar();
        while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();}
        while(c >= '0' && c <= '9'){x = (x<<1)+(x<<3)+(c^48);c = getchar();}
        return x*f;
    }
    void print(int x){
        if(x < 0){x = -x;putchar('-');}
        if(x > 9) print(x/10);
        putchar(x%10+'0');
    }
}
using namespace FASTIO;
void FRE(){
/*freopen("data1.in","r",stdin);
freopen("data1.out","w",stdout);*/}

int n,q,tim = 0,ssize[N],son[N],id[N],rk[N],top[N],fa[N],dep[N];
LL val[N],p[N];
vector<int> G[N];
struct Node{
    int L,r;
    LL p[61];
}node[N<<2];
void dfs(int u,int ffa)
{
    ssize[u] = 1,dep[u] = dep[ffa]+1,fa[u]= ffa;
    for(auto v : G[u])
    {
        if(v == ffa) continue;
        dfs(v,u);
        ssize[u] += ssize[v];
        if(ssize[v] > ssize[son[u]]) son[u] = v;
    }
}
void dfs1(int u,int sta)
{
    top[u] = sta;
    id[u] = ++tim;
    rk[tim] = u;
    if(!son[u]) return ;
    dfs1(son[u],sta);
    for(auto v : G[u]) if(v != fa[u] && v != son[u]) dfs1(v,v);
}
void insert(LL p[],LL x)
{
    for(rg int i = 60;i >= 0;--i)
    {
        if(((x>>i)&1) == 0) continue;
        if(p[i] == 0){p[i] = x;return ;}
        else x ^= p[i];
    }
}
LL Get_Max()
{
    LL ma = 0;
    for(rg int i = 60;i >= 0;--i) 
    {
        //if(p[i]) printf("p[%d] is %lld\n",i,p[i]);
        ma = max(ma,ma^p[i]);
    }
    return ma;
}
void Merge(LL p1[],LL p2[])
{
    for(rg int i = 60;i >= 0;--i)
    {
        if(p2[i]) insert(p1,p2[i]);
    }
}
void Pushup(int idx)
{
    Merge(node[idx].p,node[idx<<1].p);
    Merge(node[idx].p,node[idx<<1|1].p);
}
void build(int L,int r,int idx)
{
    node[idx].L = L,node[idx].r = r;
    if(L == r)
    {
        insert(node[idx].p,val[rk[L]]);
        return ;
    }
    int mid = (L+r)>>1;
    build(L,mid,idx<<1);
    build(mid+1,r,idx<<1|1);
    Pushup(idx);
}
void query(int L,int r,int idx)
{
    if(node[idx].L >= L && node[idx].r <= r)
    {
        Merge(p,node[idx].p);
        return ;
    }
    int mid = (node[idx].L+node[idx].r)>>1;
    if(mid >= L) query(L,r,idx<<1);
    if(mid < r) query(L,r,idx<<1|1);
}
void tree_query(int x,int y)
{
    while(top[x] != top[y])
    {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        query(id[top[x]],id[x],1);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    query(id[x],id[y],1);
}
int main()
{
    n = read(),q = read();
    for(rg int i = 1;i <= n;++i) val[i] = read();
    for(rg int i = 1;i < n;++i)
    {
        int u,v;u = read(),v = read();
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1,0);dfs1(1,0);build(1,n,1);
    while(q--)
    {
        int x,y;x = read(),y = read();
        memset(p,0,sizeof(p));
        tree_query(x,y);
        LL ans = Get_Max();
        printf("%lld\n",ans);
    }
    system("pause");
}


/*
6 10
1 3 11 5 7 9
1 2
2 4
2 5
1 3
3 6
*/
View Code

思路2:我们可以发现,对于我们想要查询的点,我们必定要保证它在x->y的路径上才行。

那么我们用p[i][j]来表示1->i的线性基。然后从父节点往下转移即可。然后我们保证j位置上的线性基的深度尽量大,这样就能使线性基尽量在链上

每次都插入i点去更新线性基。

注意的是,这里更新线性基,不引入参数数组,就会很难写。

对于j位置如果当前的点深度更大,那么j位置的线性基显然要更新为当前点的价值。

那么当前点的价值显然不能再去更新低点(就像一开始没有值一样)

此时我们的值就应该变为原来的j位置的线性基(因为它现在被挤下来了),所以用它去更新。(这里也是最麻烦的地方)

然后就对于x,y都进行一次线性基的更新即可。

// Author: levil
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
const int N = 2e4+5;
const int M = 1e4;
const LL Mod = 1e9+7;
#define rg register
#define pi acos(-1)
#define INF 1e18
#define CT0 cin.tie(0),cout.tie(0)
#define IO ios::sync_with_stdio(false)
#define dbg(ax) cout << "now this num is " << ax << endl;
namespace FASTIO{
    inline LL read(){  
        LL x = 0,f = 1;char c = getchar();
        while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();}
        while(c >= '0' && c <= '9'){x = (x<<1)+(x<<3)+(c^48);c = getchar();}
        return x*f;
    }
    void print(int x){
        if(x < 0){x = -x;putchar('-');}
        if(x > 9) print(x/10);
        putchar(x%10+'0');
    }
}
using namespace FASTIO;
void FRE(){
/*freopen("data1.in","r",stdin);
freopen("data1.out","w",stdout);*/}

LL v[N],p[N][65];//1~i位置对应的线性基。
vector<int> G[N];
int dep[N],lg[N],f[N][30],pos[N][65];//pos记录线性基的位置,确保深度最大
void init()
{
    lg[0] = 1;
    for(rg int i = 1;i < N;++i) lg[i] = lg[i-1] + ((1<<lg[i-1]) == i);
}
void update(int u,LL p[],int pos[])
{
    LL x = v[u];
    for(rg int i = 61;i >= 0;--i)
    {
        if(((x>>i)&1) == 0) continue;
         if(!p[i])
        {
             p[i] = x;pos[i] = u;
            return ;
        }
        if(dep[u] > dep[pos[i]])
        {
           swap(pos[i],u);
           swap(x,p[i]);
        }
        x ^= p[i];
    }
}
void dfs(int u,int fa)
{
    dep[u] = dep[fa]+1,f[u][0] = fa;
    for(rg int i = 1;i <= lg[dep[u]];++i) f[u][i] = f[f[u][i-1]][i-1];

    for(rg int i = 61;i >= 0;--i) p[u][i] = p[fa][i],pos[u][i] = pos[fa][i];
    update(u,p[u],pos[u]);

    for(auto v : G[u]) if(v != fa) dfs(v,u);
}
int LCA(int x,int y)
{
    if(dep[x] < dep[y]) swap(x,y);
    while(dep[x] > dep[y]) x = f[x][lg[dep[x]-dep[y]]-1];
    if(x == y) return x;
    for(rg int i = lg[dep[x]]-1;i >= 0;--i) if(f[x][i] != f[y][i]) x = f[x][i],y = f[y][i];
    return f[x][0];
}
//CF1100F Ivan and Burgers
int main()
{
    init();
    int n,q;n = read(),q = read();
    for(rg int i = 1;i <= n;++i) v[i] = read();
    for(rg int i = 1;i < n;++i)
    {
        int x,y;x = read(),y = read();
        G[x].push_back(y);
        G[y].push_back(x);
    }
    dfs(1,0);
    while(q--)
    {
        int x,y;x = read(),y = read();
        int lca = LCA(x,y);
        LL f[65];
        for(rg int i = 61;i >= 0;--i) 
        {
            if(dep[pos[x][i]] >= dep[lca]) f[i] = p[x][i];
            else f[i] = 0;
        }
        for(rg int i = 61;i >= 0;--i)
        {
            if(dep[pos[y][i]] >= dep[lca])
            {
                LL val = p[y][i];
                for(rg int j = i;j >= 0;--j)
                {
                    if(((val>>j)&1) == 0) continue;
                    if(f[j] == 0){f[j] = val;break;}
                    val ^= f[j];
                }
            }
        }
        LL ans = 0;
        for(rg int i = 61;i >= 0;--i) 
        {
            ans = max(ans,ans^f[i]);
        }
        printf("%lld\n",ans);
    }
    system("pause");
}

/*
6 10
1 3 11 5 7 9
1 2
2 4
2 5
1 3
3 6
*/
View Code

 总结:两种方法相对来说树剖虽然代码多了点,但是非常容易理解。

过了下思路,自己就写出来了。。(树剖日常忘rk放上线段树。。)

第二种方法相对还是要比树剖快点。大概两个log吧。。

其实还有第三种思路。

那就是从点分治。。(orz)

posted @ 2020-08-26 17:05  levill  阅读(157)  评论(0编辑  收藏  举报