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


    前言

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

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

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

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

    题目

    洛谷

    讲解

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

    显然这道题要离线。

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

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

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

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

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

    part1 (d le b_i)

    这个部分要简单一些,我们只需要把所有的 ((a_i,b_i))(b_i) 排序,然后把满足 (dle 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))

    排序之后依然可以用 ( t 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],'
    ');
    }
    
    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

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

  • 相关阅读:
    合并排序二
    合并排序
    理解Windows消息循环机制
    直接插入排序
    关于typedef的用法总结
    迭代器的抽象
    C++基础--malloc和new的区别
    C++基础--sizeof和strlen的区别
    C++ VS编译问题--LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
    SSL--Windows下生成OpenSSL自签证书
  • 原文地址:https://www.cnblogs.com/PPLPPL/p/14585957.html
Copyright © 2020-2023  润新知