Loading

题解:P8337 [Ynoi2004] rsxc

首先这道题没有修改,应该要预处理一些东西。

对于合法的区间,假如它所有数塞进线性基里共 \(k\) 个数,那么线性基中任意几个数(包括0个)异或和都被这个区间包含,则这个区间共 \(2^k\) 种数。

于是我们得到一个性质,合法区间线性基大小为 \(k\) 且颜色数为 \(2^k\)

我们肯定要先枚举 \(k\),再预处理合法区间。

又有一个性质,\(k\) 相同的情况下,对于每个 \(i\),合法区间的左端点肯定组成区间 \([l1,l2]\),对于每个 \(j \in [l1,l2]\),区间 \([j,i]\) 是合法区间,以下称这种 \([l1,l2]\)贡献区间,称 \(i\)贡献区间对应点

对于每个 \(k\),分成两个 Part,我们先预处理出线性基大小为 \(k\) 的区间,再预处理出颜色数为 \(2^k\) 的区间,最后取交集求出每贡献区间对应点为 \(i\) 的贡献区间 \(resl_{k,i},resr_{k,i}\)\(l1,l2\))。

Part 1

线性基大小为 \(k\)

我首先想到线段树套线性基,但空间时间都炸了。

有一个神奇的黑科技,前缀线性基(CF1100F),就是每次插入线性基的数占最高位,能快速求出区间 \([l,r]\) 的线性基大小。

插入每一个 \(a_i\) 后,我们把线性基的 \(pos\) 数组(时间戳)排序,线性基大小为 \(k\) 的区间是 \([[sortedpos_{k}+1,sortedpos_{k-1}],i]\),即对于每个 \(j \in [sortedpos_{k}+1,sortedpos_{k-1}]\),区间 \([j,i]\) 是合法区间。

如果每次查询都重新排序,那太慢了,我们采取每插入一个数重排一个数的策略。

namespace Linear_Basis{
    int lb[32],pos[32],sorted_pos[32];
    inline void modify_rank(int x,int y){
        for(int i=0;i<=30;i++)
        {
            if(sorted_pos[i]==x){//找到权值为x的sorted_pos
                //由于每次换的y肯定是最大的,所以直接把y排在最大的一位
                for(int j=i;j>0;j--)
                {
                    sorted_pos[j]=sorted_pos[j-1];
                }
                sorted_pos[0]=y;
                break;
            }
        }
    }
    inline void insert(int p,int val){//插入第p个数,权值为val
        if(!val){
            return ;
        }
        int lstp=p;//记录初始的p
        for(int i=30;i>=0;i--)
        {
            if(val&(1<<i)){
                if(!lb[i]){
                    lb[i]=val;
                    pos[i]=p;
                    p=0;
                    break;
                }
                else if(pos[i]<p){
                    swap(pos[i],p);
                    swap(lb[i],val);
                }
                val^=lb[i];
            }
        }
        if(p!=lstp){//将sorted_pos数组中的p改为lstp
            modify_rank(p,lstp);
        }
    }
    inline void update(int p){
        for(int i=0;i<=30;i++)
        {
            if(!sorted_pos[i]){
                break;
            }
            resr[i+1][p]=sorted_pos[i];
            resl[i+1][p]=sorted_pos[i+1]+1;
        }
    }
}
using Linear_Basis::insert;
using Linear_Basis::update;

Part 2

颜色数为 \(2^k\)

这东西显然左右端点单调,可以双指针。

先预处理出每个点的权值上一次出现的位置 \(lst\),下一次出现的位置 \(nxt\)

//离散化
for(int i=1;i<=n;i++)
{
    b[i]=a[i];
    uni[i]=a[i];
}
sort(uni+1,uni+1+n);
int tot=unique(uni+1,uni+1+n)-uni-1;
for(int i=1;i<=n;i++)
{
    b[i]=lower_bound(uni+1,uni+1+tot,a[i])-uni;
}
//预处理lst,nxt
for(int i=1;i<=n;i++)
{
    lst[i]=0;
    nxt[i]=n+1;
}
for(int i=1;i<=n;i++)
{
    lst[i]=lstnum[b[i]];
    nxt[lstnum[b[i]]]=i;
    lstnum[b[i]]=i;
}

nxtlst

(红色为 \(lst\),蓝色为 \(nxt\)

维护两个指针 \(l1,l2\) ,加入 \(a_i\) 时,分类讨论。

  • \(lst_i \ge l2\)\(l2\) 跳到下一个权值非 \(a_{l2}\) 的位置。

  • \(lst_i < l1\)\(l1\) 跳到第一个 \(nxt_{l1}>j\) 的位置,\(l2\) 跳到第一个 \(nxt_{l2}>j\) 的位置。

再把 \(l2\) 尽量往后跳(\(nxt_{l2} \le j\))。

线性基大小为 \(k\) 的区间是 \([[l1,l2],i]\),即对于每个 \(j \in [l1,l2]\),区间 \([j,i]\) 是合法区间。

for(int i=1,colnum=2;colnum<=n;i++,colnum<<=1)//枚举k(颜色数colnum为2^k)
{
    int l1=1,l2=1,cnt=1;
    for(int j=2;j<=n;j++)
    {
        if(lst[j]>=l2){
            while(a[l2]==a[j]&&l2<j)
            {
                l2++;
            }
        }
        else if(lst[j]<l1){
            cnt++;
            if(cnt==colnum+1){
                while(nxt[l1]<=j)
                {
                    l1++;
                }
                l1++;
                while(nxt[l2]<=j)
                {
                    l2++;
                }
                l2++;
                cnt--;
            }
        }
        while(nxt[l2]<=j&&l2<j)
        {
            l2++;
        }
        if(cnt!=colnum){
            continue;
        }
        /*统计答案*/
    }
}

现在我们来看询问。

每个 \(k,i\) 预处理出 \(lstid_{k,i},nxtid_{k,i}\),表示第一个和最后一个包含 \(i\) 的贡献区间。

询问 \(\operatorname{query}(l,r)\),等价于求,每个 \(k\) 的第 \(L=lstid_{k,l}\) 个至第 \(R=nxtid_{k,r}\) 个贡献区间(\(res_{k,1} \sim res_{k,r}\))与 \([l,r]\) 的交的长度和。

注意到,\(resl_k,resr_k\) 是单调的,可以把蓝色区间分为三部分,左边散块,中间整块,右边散块。

  • \(\text{左边散块贡献} = \text{左边散块区间右端点和} - l \times \text{左边散块区间数量}\)

  • \(\text{中间整块贡献} = \text{中间整块区间长度和} = \text{中间整块区间右端点和} - \text{中间整块区间左端点和}\)

  • \(\text{右边散块贡献} = r \times \text{右边散块区间数量} - \text{右边散块区间左端点和}\)

所以我们预处理出贡献区间的左端点前缀和 \(suml\),右端点前缀和 \(sumr\),第一个左端点不少于 \(i\) 的贡献区间编号 \(maxl_i\),最后一个右端点不超过 \(i\) 的贡献区间编号 \(maxr_i\)

注意判前后的 0。

for(int i=0;i<=M;i++)//枚举k
{
    for(int j=1;j<=n;j++)//枚举i
    {
        if(!vis[i][j]){//vis表示是否有贡献区间
            resl[i][j]=0;
            resr[i][j]=0;
            if((!i)&&(!a[j])){
                vis[i][j]=true;
                resl[i][j]=pre0[j];
                resr[i][j]=j;
            }
        }
        if(vis[i][j]){
            cnt[i]++;
            resl[i][cnt[i]]=resl[i][j];
            resr[i][cnt[i]]=resr[i][j];
            lstid[i][j]=cnt[i];
            if(!nxtid[i][resr[i][cnt[i]]]){
                nxtid[i][resr[i][cnt[i]]]=cnt[i];
            }
            if(!maxl[i][resl[i][j]]){
                maxl[i][resl[i][j]]=cnt[i];
            }
            maxr[i][resr[i][j]]=cnt[i];
        }
        else{
            lstid[i][j]=lstid[i][j-1];
        }
    }
    maxr[i][0]=0;
    maxl[i][n+1]=cnt[i]+1;
    for(int j=1;j<=n;j++)
    {
        if(!maxr[i][j]){
            maxr[i][j]=maxr[i][j-1];
        }
    }
    for(int j=n;j>=1;j--)
    {
        if(!maxl[i][j]){
            maxl[i][j]=maxl[i][j+1];
        }
    }
    nxtid[i][n+1]=cnt[i]+1;
    for(int j=n;j>=1;j--)
    {
        if(!nxtid[i][j]){
            nxtid[i][j]=nxtid[i][j+1];
        }
    }
    //suml,sumr开指针省空间
    suml[i]=new long long[cnt[i]+1];
    sumr[i]=new long long[cnt[i]+1];
    for(int j=1;j<=cnt[i];j++)
    {
        suml[i][j]=suml[i][j-1]+resl[i][j];
        sumr[i][j]=sumr[i][j-1]+resr[i][j];
    }
}
long long solve(int ql,int qr){
    long long res=0;
    for(int i=0;i<=M;i++)
    {
        if(!cnt[i]){
            continue;
        }
        int L=nxtid[i][ql],R=lstid[i][qr];
        if(L>R){
            continue;
        }
        int ml=maxl[i][ql],mr=maxr[i][qr];
        //判断边界
        ml=min(ml,R+1);
        mr=max(mr,L+1);
        ml=max(ml,L);
        mr=min(mr,R);
        if(L<=ml-1){//左边散块贡献
            int len=(ml-1)-L+1;
            res+=(sumr[i][ml-1]-sumr[i][L-1])-1ll*len*ql+len;
        }
        if(mr+1<=R){//中间整块贡献
            int len=R-(mr+1)+1;
            res+=1ll*len*qr-(suml[i][R]-suml[i][mr+1])+len;
        }
        if(ml<=mr){//右边散块贡献
            int len=mr-ml+1;
            res+=(sumr[i][mr]-sumr[i][ml-1])-(suml[i][mr]-suml[i][ml-1])+len;
        }
    }
    return res;
}

最终代码

然后你就可以去卡 P8283 了。

posted @ 2025-02-09 07:03  Mathew_Miao  阅读(12)  评论(0)    收藏  举报