CF2064D 题解

首先我们要注意到一个关键点:x的最高位最多只会异或一次

在x往左走的过程中,如果遇到最高位不及x的,那么吃掉他不会影响最高位;遇到最高位大于x的,那么吃不掉,直接停了。遇到最高位等于x的,要么吃不掉,要么吃掉之后最高位异或变为了0,再遇到最高位等于x的就吃不掉了。

所以我们对于每次询问的处理步骤是,从当前x的位置,向左找到第一个最高位>=x的数wi,然后判断,如果吃不掉,那么能够吃掉的就是n到i+1,如果能吃掉,那么更新x的值,重复上述步骤。因为每次操作都会让x至少减少一位,所以上述步骤最多执行logx次

怎么快速找到从当前位置向左,第一个最高位>=x的数wi?我们记x的最高位是msb(x) (most significant bit) ,然后开一个数组pre,其中pre[i][j]代表从第i个位置向左,第一个最高位>=j的数的下标。我们对读入的w数组进行预处理得到pre数组,更新步骤为

if msb(𝑤𝑖)<𝑗 then pre𝑖,𝑗=pre𝑖−1,𝑗

else pre𝑖,𝑗=𝑖

时间复杂度为nlogx

```cpp
#include "bits/stdc++.h"
using namespace std;
typedef long long ll;
ll msb(ll x)
{
    return __builtin_clzll(0ll)- __builtin_clzll(x);
}
int main(){
    int T;
    cin>>T;
    while(T--){
        int n,q;
        cin>>n>>q;
        vector<ll> w(n+3,0),backsum(n+3,0);
        vector<vector<ll>> pre(n+3,vector<ll>(100,0));
        for(int i=1;i<=n;++i){
            cin>>w[i];
        }
        for(int i=n;i>=1;i--){
            backsum[i]=backsum[i+1] ^ w[i];//后缀和数组,便于快速更新x的值
        }
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=64;++j)
            {
                if(msb(w[i])<j)
                    pre[i][j]=pre[i-1][j];
                else
                    pre[i][j]=i;
            }
        }

        while(q--){
            ll x;
            cin>>x;
            ll cur=n+1;//当前已经吃掉了第几个
            ll temp=x;
            while(temp>0&&cur>1)
            {
                ll i=pre[cur-1][msb(temp)];
                if(i==0)
                {
                    cur=1;
                    break;
                }
                temp=x ^ backsum[i+1];
                if(w[i]>temp)
                {
                    cur=i+1;
                    break;
                }
                else
                {
                    temp=x ^ backsum[i];
                    cur=i;
                }
            }
            cout<<n+1-cur<<" ";

        }
        cout<<endl;
    }
}
```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值