LGP10743 [SEERC 2020] AND=OR 学习笔记

LGP10743 [SEERC 2020] AND=OR 学习笔记

Luogu Link

前言

我的第一道猫树分治居然是这题。看来我也是个人物。
本题解主要参考:这篇这篇

题意简述

一个数组 \(S\) 被称为是“好的”,当且仅当可以将 \(S\) 划分为两个非空子数组 \(S_o,S_a\),满足 \(S_o\) 内所有元素的 \(\texttt{OR}\) 和等于 \(S_a\) 内所有元素的 \(\texttt{AND}\) 和。

给定一个长为 \(n\) 的数组 \(A\)\(m\) 组询问,每次给定区间 \([l,r]\),问 \(\{a_l\dots a_r\}\) 是不是一个好的数组。

做法解析

以下用 \(\text{ppc}(x)\) 作为 \(\text{popcount}(x)\) 的缩写。

首先思考:怎么判定一个数组好不好?

我们不妨设这两个子数组各自的和结果都等于 \(x\),考虑枚举 \(x\) 的值来判定。又因为 \(\texttt{OR}\) 和随运算单调不降,\(\texttt{AND}\) 和随运算单调不增,这就意味着 \(S_o\) 中所有元素都不大于 \(x\)\(S_a\) 中所有元素都不小于 \(x\)。我们发现这就意味着当我们把询问的区间内的元素从大到小排序后,\(S_o\) 就是其一段前缀,\(S_a\) 就是其一段后缀。我们枚举分界点就可以 \(O(n)\) 判定一个询问了。

显然 \(O(n)\) 一个询问还是太慢了。怎么优化?我们这次考虑枚举 \(p=\text{ppc}(x)\)。为什么呢?因为 \(\text{ppc}\) 和有着和原值类似的性质:\(\text{ppc}(S_o)\) 随元素加入单调不降,\(\text{ppc}(S_a)\) 随元素加入单调不增。我们将询问的区间内的元素按照 \(\text{ppc}(a_i)\) 值从小到大排序,那么仍有“\(S_o\) 就是其一段前缀,\(S_a\) 就是其一段后缀”。

不过,现在对于那些:\(\text{ppc}(a_i)=p\) 的元素,我们要把它丢到哪个集合呢?

如果没有这样的元素就无需讨论。如果这样的元素只有一种,我们无非就三种选择:丢到 \(S_o\)、丢到 \(S_a\),(当这种元素不止一个时)两边都丢。决策的种类是 \(O(1)\) 的。

否则我们就能直接判定它不合法了。为什么呢?当我们存在 \(a_i\neq a_j\)\(\text{ppc}(a_i)=\text{ppc}(a_j)=p\) 时,显然会有 \(\text{ppc}(a_i|a_j)>\text{ppc}(a_i)=p\)\(\text{ppc}(a_i\& a_j)<\text{ppc}(a_i)=p\)。所以显然两个集合一边最多有一个 \(\text{ppc}(a_i)=p\) 的。但这个时候一定有 \(\text{ppc}(S_o)\ge p\ge \text{ppc}(S_a)\)(根据之前随元素增加位运算和变化单调性的讨论),而除非 \(a_i=a_j\),两边就取不到这个等。所以有两种就不合法。三种及以上就更不合法了。

所以我们总的来说处理每个询问的过程就是:枚举总共 \(\log V\) 种的 \(p\) 值……等下我们不可能每次都对询问区间排序,那我们怎样才能快速求得某个区间内 \(\text{ppc}(a_i)=x\)\(a_i\) 的某和呢?答案是开 \(\log V\) 棵线段树,第 \(k\) 棵树维护 \(\text{ppc}(a_i)=k\) 的所有元素的或和与和云云,然后就能用 \(\log V\log n\) 左右的复杂度拼起来各种信息。复杂度 \(O((n+q)\log V\log n)\)。这个做法的代码可参考这篇题解

考虑优化,没错这东西还能优化!!我们可以把上述方法的双劳嗝优化到单劳嗝。

我们采用一种被称作“猫树分治”或“二区间合并”的技巧。就是说,将询问离线挂在分治树上,每次处理跨过当前区间中点的询问(其它的询问递归下去),并且,将所有当前区间的询问 \([ql,qr]\) 拆成 \([ql,mid]\)\((mid,qr]\),然后直接从 \(ql\to mid\) 扫一遍就能得到所有前缀答案(所以显然这个Trick的使用前提是贡献可以简单加),后缀们的答案同理。这样时间复杂度定格在神清气爽的 \(O(n\log n+q\log V)\) 上。

代码实现

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=1e5+5,MaxVb=32,Alf=(1<<30)-1;
int N,M,A[MaxN],C[MaxN],X,Y,ans[MaxN],Vb=30;
int nxt[MaxN];map<int,int> jst;
struct quer{int l,r;}Qu[MaxN];
vector<int> Qe[MaxN<<2];
int vld[MaxN];pii pos[MaxVb][MaxN];
struct adat{
    int osum,asum,cnt;
    adat(){osum=0,asum=Alf,cnt=0;}
}res[MaxVb][MaxN];
int otmp[MaxVb],atmp[MaxVb],ctmp[MaxVb];vector<int> vec[MaxN];
int ls(int u){return u<<1;}
int rs(int u){return (u<<1)|1;}
void putqry(int u,int cl,int cr,int dl,int dr,int id){
    if(cl==cr){Qe[u].push_back(id);return;}int cmid=(cl+cr)>>1;
    if(dr<=cmid){putqry(ls(u),cl,cmid,dl,dr,id);return;}
    if(dl>cmid){putqry(rs(u),cmid+1,cr,dl,dr,id);return;}
    Qe[u].push_back(id);
}
void bimdac(int u,int cl,int cr){
    if(cl==cr){
        for(int qid : Qe[u]){
            res[C[cl]][qid].osum|=A[cl];
            res[C[cl]][qid].asum&=A[cl];
            res[C[cl]][qid].cnt++;
        }
        return;
    }
    int cmid=(cl+cr)>>1;
    for(int qid : Qe[u]){
        auto [ql,qr]=Qu[qid];
        vec[ql].push_back(qid),vec[qr].push_back(qid);
    }
    fill(otmp,otmp+Vb+1,0),fill(atmp,atmp+Vb+1,Alf),fill(ctmp,ctmp+Vb+1,0);
    for(int i=cmid;i>=cl;i--){
        otmp[C[i]]|=A[i],atmp[C[i]]&=A[i],ctmp[C[i]]++;
        for(int qid : vec[i]){
            for(int p=0;p<=Vb;p++){
                auto &cdat=res[p][qid];
                cdat.osum|=otmp[p];
                cdat.asum&=atmp[p];
                cdat.cnt+=ctmp[p];
            }
        }
    }
    fill(otmp,otmp+Vb+1,0),fill(atmp,atmp+Vb+1,Alf),fill(ctmp,ctmp+Vb+1,0);
    for(int i=cmid+1;i<=cr;i++){
        otmp[C[i]]|=A[i],atmp[C[i]]&=A[i],ctmp[C[i]]++;
        for(int qid : vec[i]){
            for(int p=0;p<=Vb;p++){
                auto &cdat=res[p][qid];
                cdat.osum|=otmp[p];
                cdat.asum&=atmp[p];
                cdat.cnt+=ctmp[p];
            }
        }
    }
    for(int qid : Qe[u]){
        auto [ql,qr]=Qu[qid];
        vec[ql].clear(),vec[qr].clear();
    }
    bimdac(ls(u),cl,cmid),bimdac(rs(u),cmid+1,cr);
}
int So[MaxVb],Sa[MaxVb],To[MaxVb],Ta[MaxVb];
int main(){
    readis(N,M);
    for(int i=1;i<=N;i++)readi(A[i]),C[i]=binppci(A[i]);
    for(int i=N;i>=1;i--)nxt[i]=jst[A[i]]?jst[A[i]]:N+1,jst[A[i]]=i;
    for(int p=0;p<=Vb;p++){
        for(int i=1;i<=N;i++)vld[i]=(C[i]==p)?A[i]:-1;
        pii cur={N+1,N+1};
        auto &[tl,tr]=cur;
        for(int i=N;i>=1;i--){
            if(vld[i]!=-1){
                if(tl==N+1)tl=i;
                else if(tr==N+1)(A[tl]==A[i])?(tl=i):(tr=tl,tl=i);
                else (A[tl]==A[i])?(tl=i):(tr=tl,tl=i);
            }
            pos[p][i]=cur;
        }
    }
    for(int i=1;i<=M;i++)readis(X,Y),Qu[i]={X,Y},putqry(1,1,N,X,Y,i);
    bimdac(1,1,N);
    for(int q=1,cans;q<=M;q++){
        auto [cl,cr]=Qu[q];cans=0;
        if(cl==cr){puts("NO");continue;}
        for(int p=0;p<=Vb;p++){
            auto [co,ca,cc]=res[p][q];
            So[p]=co,Sa[p]=ca,To[p]=Ta[p]=cc;
        }
        for(int p=1;p<=Vb;p++)So[p]|=So[p-1],To[p]+=To[p-1];
        for(int p=Vb-1;p>=0;p--)Sa[p]&=Sa[p+1],Ta[p]+=Ta[p+1];
        for(int p=0,cso,csa,cto,cta,ccnt;p<=Vb;p++){
            cso=0,csa=Alf,cto=cta=0;
            if(p>0)cso=So[p-1],cto=(To[p-1]>0);
            if(p<Vb)csa=Sa[p+1],cta=(Ta[p+1]>0);
            auto [tl,tr]=pos[p][cl];
            ccnt=(tl<=cr)+(tr<=cr);
            minner(tl,cr),minner(tr,cr);
            if(ccnt==0&&cso==csa&&cto&&cta){cans=1;break;}
            if(ccnt==1){
                cans|=((cso|A[tl])==csa&&cta);
                cans|=(cso==(csa&A[tl])&&cto);
                cans|=(nxt[tl]<=cr&&(cso|A[tl])==(csa&A[tl]));
                if(cans)break;
            }
        }
        puts(cans?"YES":"NO");
    }
    return 0;
}

反思总结

融合位运算套路与猫树分治的好题。

只是不知道,OR=XOR和AND=XOR可做否?

posted @ 2025-05-14 22:07  矞龙OrinLoong  阅读(6)  评论(0)    收藏  举报