牛客20308,洛谷3292 [SCOI2016]幸运数字

本文详细记录了解决[SCOI2016]幸运数字问题的过程,从最初的树剖+线段树方案到优化后的树上倍增维护线性基。作者经历了算法复杂度分析错误导致的TLE,最终通过优化查询策略,将复杂度降低到O(q log3 n),实现了AC。题解部分给出了问题的本质和解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接

https://www.luogu.org/problemnew/show/P3292
https://ac.nowcoder.com/acm/problem/20308

做题的经过(可以跳过)

这不是树上倍增维护线性基的裸题吗
然而我写了一整天
故事是这样的:
我先写了个自认为 q l o g 3 qlog^3 qlog3的树剖+线段树维护区间线性基,然后 T T T了,事后发现原来这个算法的复杂度是 q × ( 6 0 2 ) × l o g 2 n q\times (60^2) \times log^2n q×(602)×log2n(这不妥妥 T T T掉吗)
然后我又写了个真的是 q l o g 3 qlog^3 qlog3的倍增,谁知道洛谷机器慢的要死,还是 T T T
然后我又写了个 q l o g 2 qlog^2 qlog2的倍增,洛谷上还是 T L E TLE TLE,我就开始怀疑人生,结果开了 O 2 O2 O2就过了,还挺快…

题解

就是个裸题,树上倍增维护线性基
查询的时候,最直接的做法就是一边往上跳一边查询,这样复杂度是 O ( q   l o g 2 n   l o g 2 2 60 ) O(q\ log_2n\ log_22^{60}) O(q log2n log2260)
优化可以效仿序列上的 R M Q RMQ RMQ,先求 l c a lca lca,这样我就知道了我要跳的区间的长度,这样求出 l o g 2 ( log_2( log2(区间长度 ) ) ),就可以把原来的 O ( l o g 2 n ) O(log_2n) O(log2n)次合并线性基优化到合并 O ( 1 ) O(1) O(1)

代码

#include <bits/stdc++.h>
#define maxn 20010
#define maxk 15
#define cl(x) memset(x,0,sizeof(x))
using namespace std;
typedef long long ll;
ll g[maxn], N, Q;
struct Graph
{
    int etot, head[maxn], to[maxn<<1], next[maxn<<1], w[maxn<<1], N;
    void clear()
    {
        int i;
        for(i=1;i<=N;i++)head[i]=0;
        for(i=1;i<=etot;i++)to[i]=next[i]=w[i]=0;
        N=etot=0;
    }
    void adde(int a, int b, int c){to[++etot]=b;w[etot]=c;next[etot]=head[a];head[a]=etot;}
}G;
ll read(ll x=0)
{
    ll c, f(1);
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-f;
    for(;isdigit(c);c=getchar())x=x*10+c-0x30;
    return f*x;
}
struct LinearBasis
{
    ll b[70];
    void clear(){cl(b);}
    void insert(ll t)
    {
        for(ll k(59);~k;k--)
            if(t&(1ll<<k))
            {
                if(!b[k])b[k]=t;
                t^=b[k];
            }
    }
    void insert(ll *a, ll len)
    {
        for(auto i(0);i<len;i++)insert(a[i]);
    }
    bool check(ll x)
    {
        for(ll k(59);~k;k--)
            if(x&(1ll<<k))x^=b[k];
        return !x;
    }
};
struct Doubling_LCA
{
    int f[maxn][maxk+2], depth[maxn];
    LinearBasis lb[maxn][maxk+2];
    void clear(){cl(f), cl(lb), cl(depth);}
    void dfs(Graph &G, int pos, int pre)
    {
        for(auto k=1;(1<<k)<=depth[pos];k++)f[pos][k]=f[f[pos][k-1]][k-1];
        for(auto k=1;(1<<k)<=depth[pos];k++)
        {
            lb[pos][k]=lb[pos][k-1];
            lb[pos][k].insert(lb[f[pos][k-1]][k-1].b,63);
        }
        for(auto p(G.head[pos]);p;p=G.next[p])
            if(G.to[p]!=pre)
            {
                f[G.to[p]][0]=pos;
                lb[G.to[p]][0].insert(g[G.to[p]]);
                depth[G.to[p]]=depth[pos]+1;
                dfs(G,G.to[p],pos);
            }
    }
    void run(Graph &G, int root)
    {
        depth[root]=1;
        lb[root][0].insert(g[root]);
        dfs(G,root,0);
    }
    int q(int x, int y)
    {
        if(depth[x]<depth[y])swap(x,y);
        for(auto k(maxk);~k;k--)
            if(depth[f[x][k]]>=depth[y])
                x=f[x][k];
        if(x==y)return x;
        for(auto k(maxk);~k;k--)
            if(f[x][k]!=f[y][k])
                x=f[x][k], y=f[y][k];
        return f[x][0];
    }
}dlca;
ll q(int x, int y)
{
    int lca=dlca.q(x,y), K, p, d;
    ll ans(0);
    LinearBasis lb; lb.clear();
    d=dlca.depth[x]-dlca.depth[lca]+1;
    K=log2(d);
    lb.insert(dlca.lb[x][K].b,59);
    for(auto k(maxk);~k;k--)if( d-(1<<K) & (1<<k) )x=dlca.f[x][k];
    lb.insert(dlca.lb[x][K].b,59);

    d=dlca.depth[y]-dlca.depth[lca]+1;
    K=log2(d);
    lb.insert(dlca.lb[y][K].b,59);
    for(auto k(maxk);~k;k--)if( d-(1<<K) & (1<<k) )y=dlca.f[y][k];
    lb.insert(dlca.lb[y][K].b,59);

    for(auto k(59);~k;k--)if( (ans&(1ll<<k))==0 )ans^=lb.b[k];

    return ans;
}
void init()
{
    ll i, u ,v;
    N=read(); Q=read();
    for(i=1;i<=N;i++)g[i]=read();
    for(i=1;i<N;i++)u=read(), v=read(), G.adde(u,v,0), G.adde(v,u,0);
    dlca.clear();
    dlca.run(G,1);
}
int main()
{
    init();
    while(Q--)
    {
        auto x=read(), y=read();
        printf("%lld\n",q(x,y));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值