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

前言

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

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

update 2021.4.4 出分了,只有40pts,暂时不知道哪里出了问题,等数据出来。

update 2021.8.17 偶然点开以前的文章,感觉好像是线段树分治。(不过锅是找不到了,数据没公开)

题目

洛谷

讲解

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

显然这道题要离线。

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

考虑把询问丢到线段树上面,也就是把每个询问分成约 \(log_2n\) 个,放到线段树上,这样我们如果可以处理出每个区间对应的询问,那么我们就可以做到 \(O(n\log_2n\log_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_i\)\(a_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的信息。

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

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

\(a=0,b=0\)\(c\) 必为 \(0\),即走 \(0\) 边。

\(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\) 边继续更新。

这个地方的讲解可能有些复杂,建议与代码一起食用。

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。

//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

有没有大佬告诉我这个做法是不是线段树分治啊。

posted @ 2021-03-27 15:41  皮皮刘  阅读(87)  评论(0编辑  收藏  举报