P7470 [NOI Online 2021 提高组] 岛屿探险

· · 题解

前言

说实话,我并不知道我用的这个做法叫什么,我开始以为他是整体二分,然后发现我写出来的东西可能是线段树分治(然而我根本就不知道线段树分治是个啥)。

而且这个做法可能被卡,因为我本题测出来极限数据为2.9s,卡常后为2.4s。不过这份代码暂时可以在洛谷上过民间数据。

题目

洛谷

讲解

本来这道题我是一点都不会的,但是我们完全可以根据套路把这道题做出来,我直接把草稿搬上来(部分删改):

显然这道题要离线。

由于这个 \tt min 很不好搞,猜测是对 d 进行排序,然后用数据结构维护 a\oplus c ,最后分别算最小值是 b 或者 d 的两个部分的贡献。

考虑把询问丢到线段树上面,也就是把每个询问分成约 log_2n 个,放到线段树上,这样我们如果可以处理出每个区间对应的询问,那么我们就可以做到 O(nlog_2nlog_2w) 了。

之后的讲解如果提到全部询问等字眼,指的是线段树中某个区间的全部询问

这里我把取等条件是算在 d 里面的。

把式子写在这里:a_i\oplus c \le min(b_i,d) (c,d没有下标相当于我们考虑信息多组,询问为一组)。

part1 d \le b_i

这个部分要简单一些,我们只需要把所有的 (a_i,b_i)b_i 排序,然后把满足 d\le b_ia_i 扔到字典树里面去。

显然所有询问 (c,d) 也需要排序,用 two-pointer 维护插入的信息。

对于每个询问我们把一组 (c,d) 扔进去跑即可。

part2 b_i < d

在上一个部分我们根据 a_i,c,d 可以算出有多少个位置满足条件,但是现在我们的信息是 a_i,b_i,c,存在两个变元,这个又怎么做呢?

emmm,知3求2,我们可以把 a_i,b_i 扔到字典树里面去,显然我们可以推出 c 在某个范围是可以得到贡献的,最后把 c 放进去跑,累加答案即可。

当然这里我们依然要对 (a_i,b_i) 排序,只是这个时候要扔到字典树里面的是 b_i < d(a_i,b_i)

排序之后依然可以用 \tt two-pointer 维护插入的信息,但是这里我们发现上面插入的和这里插入的信息互为补集,所以我们需要先把part1的信息全部插入,然后按顺序插入part2的信息,删除part1的信息。

具体的,对于这棵字典树,我们这样操作(aa_i 二进制下当前位置的数字,b,c 同理):

更新字典树的过程是抵着上限的(仔细揣摩这句话),也就是说在之前 a_ib_i 是相等的。

$a=0,b=1$,此时如果 $c$ 为 $0$,显然可以直接获得贡献,在 $0$ 边加个1,那么走 $1$ 边继续更新。 $a=1,b=0$,$c$ 必为 $1$,即走 $1$ 边。 $a=1,b=1$,此时如果 $c$ 为 $1$,显然可以直接获得贡献,在 $1$ 边加个1,那么走 $0$ 边继续更新。 这个地方的讲解可能有些复杂,建议与代码一起食用。 ```cpp void ins1(Island x) { int now = 0; for(int i = 23;i >= 0;-- i) { if(!t1[now].ch[0]) t1[now].ch[0] = ++tot1; if(!t1[now].ch[1]) t1[now].ch[1] = ++tot1; int aa = (x.a >> i) & 1,bb = (x.b >> i) & 1; if(!aa) { if(!bb) now = t1[now].ch[0]; else t1[t1[now].ch[0]].cnt++,now = t1[now].ch[1]; } else { if(!bb) now = t1[now].ch[1]; else t1[t1[now].ch[1]].cnt++,now = t1[now].ch[0]; } if(!i) t1[now].cnt++;//走到最后还是相等的,不要忘了这个是要算贡献的。 } } ``` 至此我的做法已经全部讲完了,最后提醒一句,数据结构一定要对拍,而且记得跑大数据,多个机房大佬因为树套树空间等问题爆炸。 # 代码 考场代码,去调试信息版本,为了卡常我都改成归并排序了QAQ。(丑陋) 为了讲解更顺畅,我调换了part1,2的顺序,在代码中ins1,query1其实属于part2,ins2,query2属于part1。 ```cpp //12252024832524 #include <cstdio> #include <vector> #include <cstring> #include <algorithm> #define TT template<typename T> using namespace std; typedef long long LL; const int MAXN = 100005; int n,Q,A[MAXN],B[MAXN],ans[MAXN]; LL Read() { LL x = 0,f = 1;char c = getchar(); while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();} while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();} return x * f; } TT void Put1(T x) { if(x > 9) Put1(x/10); putchar(x%10^48); } TT void Put(T x,char c = -1) { if(x < 0) putchar('-'),x = -x; Put1(x); if(c >= 0) putchar(c); } TT T Max(T x,T y){return x > y ? x : y;} TT T Min(T x,T y){return x < y ? x : y;} TT T Abs(T x){return x < 0 ? -x : x;} struct Query { int c,d,ID; bool operator < (const Query &px)const{ return d < px.d; } }q[MAXN]; vector<Query> t[MAXN<<2]; int now; #define lc (x<<1) #define rc (x<<1|1) void insq(int x,int l,int r,int ql,int qr) { if(ql <= l && r <= qr) { t[x].push_back(q[now]); return; } int mid = (l+r) >> 1; if(ql <= mid) insq(lc,l,mid,ql,qr); if(mid+1 <= qr) insq(rc,mid+1,r,ql,qr); } int tot1,tot2; struct Trie { int ch[2],cnt; }t1[MAXN * 48],t2[MAXN * 48];//b_i小于d & d小于等于b struct Island { int a,b; Island(){} Island(int a1,int b1){ a = a1; b = b1; } bool operator < (const Island &px)const{ return b < px.b; } }I[MAXN],I2[MAXN]; void ins1(Island x) { int now = 0; for(int i = 23;i >= 0;-- i) { if(!t1[now].ch[0]) t1[now].ch[0] = ++tot1; if(!t1[now].ch[1]) t1[now].ch[1] = ++tot1; int aa = (x.a >> i) & 1,bb = (x.b >> i) & 1; if(!aa) { if(!bb) now = t1[now].ch[0]; else t1[t1[now].ch[0]].cnt++,now = t1[now].ch[1]; } else { if(!bb) now = t1[now].ch[1]; else t1[t1[now].ch[1]].cnt++,now = t1[now].ch[0]; } if(!i) t1[now].cnt++; } } void ins2(int x,int val) { int now = 0; for(int i = 23;i >= 0;-- i) { int to = (x >> i) & 1; if(!t2[now].ch[to]) t2[now].ch[to] = ++tot2; now = t2[now].ch[to]; t2[now].cnt += val; } } int query1(int x) { int now = 0,ret = 0; for(int i = 23;i >= 0;-- i) { int to = (x >> i) & 1; if(!t1[now].ch[to]) return ret; now = t1[now].ch[to]; ret += t1[now].cnt; } return ret; } int query2(int c,int d) { int now = 0,ret = 0; for(int i = 23;i >= 0;-- i) { if(!now && i != 23) return ret; int cc = (c >> i) & 1,dd = (d >> i) & 1; if(!dd) { if(!cc) now = t2[now].ch[0]; else now = t2[now].ch[1]; } else { if(!cc) ret += t2[t2[now].ch[0]].cnt,now = t2[now].ch[1]; else ret += t2[t2[now].ch[1]].cnt,now = t2[now].ch[0]; } if(!i && now) ret += t2[now].cnt;//走到最后还是相等,记得累加 } return ret; } void dfs(int x,int l,int r) { int mid = (l+r) >> 1; if(l != r) dfs(lc,l,mid),dfs(rc,mid+1,r); if(l == r) I[l] = Island(A[l],B[l]); int i1 = l,j1 = mid+1,k1 = l; while(i1 <= mid && j1 <= r) if(I[i1] < I[j1]) I2[k1++] = I[i1++]; else I2[k1++] = I[j1++]; while(i1 <= mid) I2[k1++] = I[i1++]; while(j1 <= r) I2[k1++] = I[j1++]; int now = l; for(int i = l;i <= r;++ i) I[i] = I2[i],ins2(I[i].a,1);//插入信息互为补集,我们要提前插入part1的信息 for(int i = 0,len = t[x].size();i < len;++ i) { while(now <= r && I[now].b < t[x][i].d) ins1(I[now]),ins2(I[now].a,-1),now++;//插入part2的信息,删除part1的信息 ans[t[x][i].ID] += query1(t[x][i].c) + query2(t[x][i].c,t[x][i].d); } for(int i = 0;i <= tot1;++ i) t1[i].ch[0] = t1[i].ch[1] = t1[i].cnt = 0; tot1 = 0; for(int i = 0;i <= tot2;++ i) t2[i].ch[0] = t2[i].ch[1] = t2[i].cnt = 0; tot2 = 0; } int lq[MAXN],rq[MAXN]; void solve2() { for(int i = 1;i <= Q;++ i) { lq[i] = Read(),rq[i] = Read(); q[i].c = Read(),q[i].d = Read(),q[i].ID = i; } sort(q+1,q+Q+1); for(int i = 1;i <= Q;++ i) now = i,insq(1,1,n,lq[q[i].ID],rq[q[i].ID]); dfs(1,1,n); for(int i = 1;i <= Q;++ i) Put(ans[i],'\n'); } int main() { // freopen("island.in","r",stdin); // freopen("island.out","w",stdout); n = Read(); Q = Read(); for(int i = 1;i <= n;++ i) A[i] = Read(),B[i] = Read(); solve2(); return 0; } ``` # By the way 有没有大佬告诉我这个做法是不是线段树分治啊。 ~~似乎这个东西长得更像cdq分治?~~