[NOI Online 2021] 岛屿探险
或许更加自然的思路。
先描述一下考场思路吧,大概是我们不妨先考虑部分分。
对于 \(\max \{d_i\} \le \min \{ b_i\}\)
容易发现,答案这种性质下与 \(b\) 没有任何关系,那么我们不妨考虑在已知一组 \(c_i , d_i\) 的情况下,满足什么条件的 \(a_i\) 是合法的。我们不妨定义 \(x_i = c_i ⊕ d_i\),则假设在二进制的从大到小第 \(j\) 位上 \(c\) 与 \(x\) 是不同的,则 \(a\) 只要满足从大到小前 \(j-1\) 位与 \(x\) 相同,同时第 \(j\) 位与 \(c\) 相同,则后面的位我们完全可以随便选而一定可以满足 \(a ⊕ c_i < x ⊕ c_i = d_i\)。
通过以上操作我们可以将合法的 \(a\) 所处区间拆成值域连续的不超过 \(\log_{2}^{A}\) 段,大概可以用树状数组单点修改前缀查询。
对于 \(\max \{b_i\} \le \min \{ d_i\}\)
同理,我们可以把对于每个 \(a_i,b_i\) 的满足条件的 \(c_i\) 所处值域拆为值域连续的不超过 \(log_{2}^{A}\) 段。我们可以用树状数组区间差分修改,单点查询。
代码大概长这个样子:
void solve2() {
for (int i = 1; i <= Q; ++i) {
if(q[i].l > 1) num[q[i].l - 1].push_back(mp(i , -1));
num[q[i].r].push_back(mp(i , 1));
}
for (int i = 1; i <= n; ++i) {
update(a[i] , 1);
for (int j = 0; j < (int)num[i].size(); ++j) {
int x = num[i][j].fs , op = num[i][j].sc , c = q[x].c , d = q[x].d , bad = d ^ c , cur = 0;
for (int k = 24; k >= 0; --k) {
if(((c >> k) & 1) != ((bad >> k) & 1)) {
cur ^= (c & (1 << k));
ans[x] = (ans[x] + op * (find(cur ^ ((1 << k) - 1)) - find(cur - 1)));
cur ^= (c & (1 << k));
}
cur ^= (bad & (1 << k));
}
ans[x] = (ans[x] + op * (find(bad) - find(bad - 1)));
}
}
for (int i = 1; i <= Q; ++i) write(ans[i]);
}
void solve3() {
for (int i = 1; i <= Q; ++i) {
if(q[i].l > 1) num[q[i].l - 1].push_back(mp(i , -1));
num[q[i].r].push_back(mp(i , 1));
}
for (int i = 1; i <= n; ++i) {
int c = a[i] , d = b[i] , bad = d ^ c , cur = 0;
for (int k = 24; k >= 0; --k) {
if(((c >> k) & 1) != ((bad >> k) & 1)) {
cur ^= (c & (1 << k));
update((cur ^ ((1 << k) - 1)) + 1 , -1);
update(cur , 1);
cur ^= (c & (1 << k));
}
cur ^= (bad & (1 << k));
}
update(bad + 1 , -1);
update(bad , 1);
for (int j = 0; j < (int)num[i].size(); ++j) {
int x = num[i][j].fs , op = num[i][j].sc;
ans[x] = ans[x] + op * find(q[x].c);
}
}
for (int i = 1; i <= Q; ++i) write(ans[i]);
}
复杂度大概是 \(O(n(\log_2^n)^2)\)
对于 \(L_i=1,R_i=n\)。
这个也比较简单,排个序分类处理一下就好了。
考虑能否将特殊性质扩展出来,我们发现如果我们将询问拆开,拆出来的连续值域拆开计算,并考虑上 \(b_i\) 和 \(d_i\) 在大小关系上的限制分类讨论(令 \(b_i < d_i\) 什么的),可以很容易的发现这是一个三维偏序的模型。
于是我们得到了一个 \(O(n(\log_2^n)^3)\) 的做法
(不如暴力的做法)。
但是由于我的菜,我在考场上此时就已经绝望开始写特殊性质了。
但实际上此时我们离正解只有一步之遥了。
我们发现复杂度的瓶颈在于我们既要将满足条件的连续值域拆分出来又需要使用数据结构去维护它。考虑能否使用一个新的数据结构同时完成上面两类操作。
仔细观察,不难发现,我们拆出来的值域大概是前面一段确定的,加上后面的随便选,这恰好可以与字典树的一棵子树完全对应,因此我们的任务变成了找到这些子树并打上标记,而这些子树恰好又全部连在了 \(x_i\) 的那样一条链上,于是我们把两只 \(log\) 优化成了一只 \(log\) 。
同时我们使用cdq分治的复杂度也降低到了两只 \(log\) ,可以通过本题。
代码大概是这样的:
#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;
template <typename T>
void read(T &x){
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s^'0');s=getchar();}
x*=f;
}
const int MAXN = 1e5 + 5;
struct TrieB {
int tr[MAXN * 24][2] , cnt;
void pre_Insert(int x) {
int cur = 1;
for (int i = 24; i >= 0; --i) {
int k = (x >> i) & 1;
if(!tr[cur][k]) tr[cur][k] = ++cnt;
cur = tr[cur][k];
}
}
int num[MAXN * 30];
void push_up(int x) {num[x] = num[tr[x][0]] + num[tr[x][1]];}
void Insert(int x) {
int tmp[30] = {} , t = 0;
int cur = 1;
queue <int> q;
for (int i = 24; i >= 0; --i) {
int k = (x >> i) & 1;
tmp[++t] = cur;
cur = tr[cur][k];
}
if(cur) num[cur] ++;
num[0] = 0;
for (int i = t; i >= 1; --i) push_up(tmp[i]);
}
void Delete(int x) {
int tmp[30] = {} , t = 0;
int cur = 1;
for (int i = 24; i >= 0; --i) {
int k = (x >> i) & 1;
tmp[++t] = cur;
cur = tr[cur][k];
}
if(cur) num[cur] --;
num[0] = 0;
for (int i = t; i >= 1; --i) push_up(tmp[i]);
}
int find(int c , int d) {
int cur = 1 , res = 0;
for (int i = 24; i >= 0; --i) {
int kc = (c >> i) & 1 , kd = (d >> i) & 1;
if((kc ^ kd) != kc) res += num[tr[cur][kc]];
cur = tr[cur][kc ^ kd];
}
if(cur) res += num[cur];
return res;
}
}B;// for each d < b
struct TrieA {
int tr[MAXN * 24][2] , cnt;
void pre_Insert(int x) {
int cur = 1;
for (int i = 24; i >= 0; --i) {
int k = (x >> i) & 1;
if(!tr[cur][k]) tr[cur][k] = ++cnt;
cur = tr[cur][k];
}
}
int num[MAXN * 30];
void push_up(int x) {num[x] = num[tr[x][0]] + num[tr[x][1]];}
void Insert(int c , int d) {
int cur = 1;
for (int i = 24; i >= 0; --i) {
int kc = (c >> i) & 1 , kd = (d >> i) & 1;
if((kc ^ kd) != kc) num[tr[cur][kc]] ++;
cur = tr[cur][kc ^ kd];
}
if(cur) num[cur] ++;
}
void Delete(int c , int d) {
int cur = 1;
for (int i = 24; i >= 0; --i) {
int kc = (c >> i) & 1 , kd = (d >> i) & 1;
if((kc ^ kd) != kc) num[tr[cur][kc]] --;
cur = tr[cur][kc ^ kd];
}
if(cur) num[cur] --;
}
int find(int x) {
int cur = 1 , res = 0;
for (int i = 24; i >= 0; --i) {
int k = (x >> i) & 1;
cur = tr[cur][k];
res += num[cur];
}
return res;
}
}A;// for each b < d
int n , Q , a[MAXN] , b[MAXN];
struct query {
int pos , c , d , op;
query () {}
query (int P , int C , int D , int I) {pos = P , c = C , d = D , op = I;}
}q[MAXN * 3] , tmp[MAXN * 3];
bool cmp(query x , query y) {
return x.d < y.d;
}
int ans[MAXN];
void cdq(int l , int r) {
if(l == r) return;
int mid = (l + r) >> 1;
cdq(l , mid) , cdq(mid + 1 , r);
// update to posi < posi+1 , leftc < rightc
int i = l , j = mid + 1;
while(i <= mid && j <= r) {
if(q[i].pos < q[j].pos) {
if(q[i].op) {
if(q[i].op < 0) ans[-q[i].op] -= B.find(q[i].c , q[i].d);
else ans[q[i].op] += B.find(q[i].c , q[i].d);
}
i ++;
}
else {
if(!q[j].op) B.Insert(q[j].c);
j ++;
}
}
while(i <= mid) {
if(q[i].op) {
if(q[i].op < 0) ans[-q[i].op] -= B.find(q[i].c , q[i].d);
else ans[q[i].op] += B.find(q[i].c , q[i].d);
}
i ++;
}
while(j <= r) {
if(!q[j].op) B.Insert(q[j].c);
j ++;
}
i = l , j = mid + 1;
int cnt = l - 1;
while(i <= mid && j <= r) {
if(q[i].pos <= q[j].pos) {
if(!q[i].op) A.Insert(q[i].c , q[i].d);
tmp[++cnt] = q[i];
i ++;
}
else {
if(q[j].op) {
if(q[j].op < 0) ans[-q[j].op] -= A.find(q[j].c);
else ans[q[j].op] += A.find(q[j].c);
}
tmp[++cnt] = q[j];
j ++;
}
}
while(i <= mid) {
if(!q[i].op) A.Insert(q[i].c , q[i].d);
tmp[++cnt] = q[i];
i ++;
}
while(j <= r) {
if(q[j].op) {
if(q[j].op < 0) ans[-q[j].op] -= A.find(q[j].c);
else ans[q[j].op] += A.find(q[j].c);
}
tmp[++cnt] = q[j];
j ++;
}
for (int i = l; i <= r; ++i) {
if(!q[i].op && i <= mid) A.Delete(q[i].c , q[i].d);
if(!q[i].op && i > mid) B.Delete(q[i].c);
q[i] = tmp[i];
}
}
int main() {
B.cnt = A.cnt = 1;
read(n),read(Q);
int cnt = 0;
for (int i = 1; i <= n; ++i) read(a[i]),read(b[i]),A.pre_Insert(a[i]),B.pre_Insert(a[i]),q[++cnt] = query(i , a[i] , b[i] , 0);
for (int i = 1; i <= Q; ++i) {
int l , r , c , d;
read(l),read(r),read(c),read(d);
if(l > 1) q[++cnt] = query(l - 1 , c , d , -i);
q[++cnt] = query(r , c , d , i);
A.pre_Insert(c),B.pre_Insert(c);
}
sort(q + 1 , q + 1 + cnt , cmp);
cdq(1 , cnt);
for (int i = 1; i <= Q; ++i) printf("%d\n" , ans[i]);
return 0;
}