【洛谷P7470】岛屿探险
题目
题目链接:https://www.luogu.com.cn/problem/P7470
凇睦是一个喜欢探险的女孩子,这天她到一片海域上来探险了。
在这片海域上一共有 \(n\) 座岛屿排成一排,标号为 \(1,2,3, \ldots ,n\)。每座岛屿有两个权值,分别为劳累度 \(a_i\) 和有趣度 \(b_i\)。
对于一座劳累度为 \(a\),有趣度为 \(b\) 的小岛,如果这个小岛满足 \((a\oplus c) \leq \min(b,d)\),凇睦到这座岛探险就会感到开心,其中 \(c\) 表示凇睦到岛上去之前就有的劳累度(称作初始劳累度),同理 \(d\) 代表凇睦的初始有趣度。\(\oplus\) 表示二进制异或(即二进制表示下不进位的加法)。
为了玩的更尽兴,凇睦会向你询问 \(q\) 次,每次给出一个区间 \([l_i,r_i]\) 和两个数 \(c_i,d_i\),你需要告诉凇睦若她的初始劳累度为 \(c_i\),初始有趣度为 \(d_i\),则有多少个标号在 \([l_i,r_i]\) 这个区间内的岛屿能使凇睦探险时感到开心。
\(n,q\leq 10^5\),\(1\leq a_i,b_i,c_i,d_i<2^{24}\)。
思路
首先考虑当询问都是 \(l=1,r=n\) 的时候怎么做。
这个 \(a\text{ xor }c\leq \min(b,d)\) 的 \(\min\) 很烦,可以把岛屿按照 \(b\) 升序排序,询问按照 \(d\) 升序排序,那么对于每一次询问,岛屿中都是一段前缀满足 \(\min(b_i,d)=b_i\),一段后缀满足 \(\min(b_i,d)=d\)。且随着 \(d\) 增大,显然这个分界点也是递增的。
分别考虑两个部分怎么做:
- 对于 \(\min(b_i,d)=d\) 的部分,询问等价于多少个 \(i\) 满足 \(a_i\text{ xor }c\leq d\)。把所有 \(a_i\) 插入到一棵 01 Trie 中,并且把满足 \(b_i<d\) 的 \(a_i\) 的结尾位置权值加 \(1\)。那么只需要查询所有 \(\text{xor } c\leq d\) 的子树和即可。可以转化为链加单点查询。
- 对于 \(\min(b_i,d)=b_i\) 的部分,询问等价于多少个 \(i\) 满足 \(a_i\text{ xor }c\leq b_i\)。容易发现,满足条件的 \(c\) 在数轴上应该是 \(O(\log V)\) 个区间(\(V\) 是值域大小),那么插入 \(i\) 时,只需要把这 \(O(\log V)\) 个区间加 \(1\),查询时查询 \(c\) 的每一位在 Trie 上节点的权值之和。
考虑询问的一般情况,只需要把 \(l,r\) 扔到线段树上拆成 \(O(\log n)\) 个区间,然后对于线段树上每一个区间都做一遍就好了。
时间复杂度 \(O(n\log n\log V)\)。
代码
跑的有点慢加了 O2 才过。
#include <bits/stdc++.h>
using namespace std;
const int N=100010,LG=24;
int n,m,ans[N];
struct node
{
int a,b;
}a[N],b[N];
struct node1
{
int c,d,id;
};
bool cmp1(node x,node y)
{
return x.b<y.b;
}
bool cmp2(node1 x,node1 y)
{
return x.d<y.d;
}
struct Trie
{
#define NN (2*N*(LG+2))
int tot,ch[NN][2],siz[NN],cnt[NN];
#undef NN
void clear()
{
for (int i=1;i<=tot;i++)
ch[i][0]=ch[i][1]=siz[i]=cnt[i]=0;
tot=1;
}
void insert(int x,int v)
{
int p=1;
for (int i=LG;i>=0;i--)
{
int c=(x>>i)&1;
if (!ch[p][c]) ch[p][c]=++tot;
p=ch[p][c]; siz[p]+=v;
}
}
void add(int &x)
{
if (!x) x=++tot;
cnt[x]++;
}
int update(int p,int k,int a,int b)
{
if (!p) p=++tot;
if (k<0) { cnt[p]++; return p; }
int c=(a>>k)&1,d=(b>>k)&1;
if (c==0 && d==0) ch[p][0]=update(ch[p][0],k-1,a,b);
if (c==0 && d==1) ch[p][1]=update(ch[p][1],k-1,a,b),add(ch[p][0]);
if (c==1 && d==0) ch[p][1]=update(ch[p][1],k-1,a,b);
if (c==1 && d==1) ch[p][0]=update(ch[p][0],k-1,a,b),add(ch[p][1]);
return p;
}
int query1(int p,int k,int a,int b)
{
if (k<0 || !p) return siz[p];
int c=(a>>k)&1,d=(b>>k)&1,res=0;
if (c==0 && d==0) res+=query1(ch[p][0],k-1,a,b);
if (c==0 && d==1) res+=query1(ch[p][1],k-1,a,b)+siz[ch[p][0]];
if (c==1 && d==0) res+=query1(ch[p][1],k-1,a,b);
if (c==1 && d==1) res+=query1(ch[p][0],k-1,a,b)+siz[ch[p][1]];
return res;
}
int query2(int p,int k,int a)
{
if (k<0 || !p) return cnt[p];
return query2(ch[p][(a>>k)&1],k-1,a)+cnt[p];
}
}trie;
struct SegTree
{
vector<node1> ask[N*4];
void update(int x,int l,int r,int ql,int qr,int c,int d,int id)
{
if (ql<=l && qr>=r)
return (void)(ask[x].push_back((node1){c,d,id}));
int mid=(l+r)>>1;
if (ql<=mid) update(x*2,l,mid,ql,qr,c,d,id);
if (qr>mid) update(x*2+1,mid+1,r,ql,qr,c,d,id);
}
void query(int x,int l,int r)
{
trie.clear();
for (int i=l;i<=r;i++)
{
a[i]=b[i];
trie.insert(a[i].a,1);
}
sort(a+l,a+r+1,cmp1);
sort(ask[x].begin(),ask[x].end(),cmp2);
for (int i=0,j=l;i<ask[x].size();i++)
{
int c=ask[x][i].c,d=ask[x][i].d;
for (;j<=r && a[j].b<d;j++)
{
trie.insert(a[j].a,-1);
trie.update(1,LG,a[j].a,a[j].b);
}
ans[ask[x][i].id]+=trie.query1(1,LG,c,d)+trie.query2(1,LG,c);
}
if (l==r) return;
int mid=(l+r)>>1;
query(x*2,l,mid); query(x*2+1,mid+1,r);
}
}seg;
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d%d",&b[i].a,&b[i].b);
for (int i=1,l,r,c,d;i<=m;i++)
{
scanf("%d%d%d%d",&l,&r,&c,&d);
seg.update(1,1,n,l,r,c,d,i);
}
seg.query(1,1,n);
for (int i=1;i<=m;i++)
cout<<ans[i]<<"\n";
return 0;
}