LGP10743 [SEERC 2020] AND=OR 学习笔记
LGP10743 [SEERC 2020] AND=OR 学习笔记
前言
我的第一道猫树分治居然是这题。看来我也是个人物。
本题解主要参考:这篇和这篇。
题意简述
一个数组 \(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可做否?
浙公网安备 33010602011771号