[THUPC 2024 初赛] 二进制 (树状数组单点删除+单点查询)(双堆模拟set)

本文主要是介绍[THUPC 2024 初赛] 二进制 (树状数组单点删除+单点查询)(双堆模拟set),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题解

题目本身不难想

首先注意到所有查询的序列长度都是小于logn级别的

我们可以枚举序列长度len,然后用类似滑动窗口的方法,一次性预处理出每种字串的所有出现位置,也就是开N个set去维护所有的位置。预处理会进行O(logn)轮,每次需要O(n*logn)的时间复杂度初始化set并计算位置。总共复杂度O(nlog^2n),看一下时间限制6s,感觉可以过23333。

删除操作可以直接暴力,直接从每种字串的位置集合中删除所有被影响到的位置,然后再把删除后字符串合并产生的新的子串加入到set中,过程中需要支持O(logn)的单点删除和单点查询。

在set中,删除起始点在L~R之间子串信息,再插入起始点在L到x-1的新构成的子串的信息

删除操作最多O(n/logn)次,每次直接暴力就是O(log^2n),总共复杂度O(nlogn)

接下来就是一些小问题,如何维护单点删除、单点查询的序列呢?

首先我们肯定不会去真正的移动序列,保留原始的输入01序列

可以想到用set去维护当前存在的每个坐标,但是支持查询第k个坐标的话得手写平衡树

也可以想到用线段树或者树状数组维护每个位置的存在信息,在线段树或者树状数组上二分来查询删除后的序列中的第k个坐标的真实位置。

这里使用树状数组

树状数组二分类似于倍增查询LCA的思想,十分易懂。

然后我们迅速写完整个内容,交一发,发现TLE了

看一下复杂度,发现瓶颈在于预处理,于是我们把初始化中对每个位置都进行树状数组二分,替换为直接使用当前位置存在信息数组进行处理,这样预处理中计算坐标的部分就变成O(n)了

但是仍然TLE了

现在瓶颈仍然是预处理,如果C++支持对有序序列O(n)建立set就好了

后来看了洛谷上题解的方法,才知道可以用两个优先队列来模拟set

由于我们只需要维护集合中的最小值以及集合的元素个数

使用两个堆,一个维护插入的内容,另一个维护删除的内容

当查询个数时,两个堆的大小相减即可。当查询最小值时,如果“删除堆”中的最小值与“插入堆”中的最小值相等,就两个一起pop掉,直到找到第一个“插入堆”中存在,但“删除堆”中不存在的元素即可。

(其实也可以用两个vector来模拟,因为对于每种子串,查询的次数只有一次,所以可以大胆排序再查询,这样初始化时间复杂度也是O(nlogn),查询删除子串的总时间复杂度是最坏O(nlog^2n)不过似乎也能过,因为sort在大部分都有序的情况下还是很快的)

改完之后,从6.18s变成了1.17s,发生了质的飞跃23333

有人可能会问,优先队列插入不也是O(logn)的吗,为什么会比set快这么多,因为预处理的过程中插入集合的内容是顺序的,根据小根堆的实现,只有当自己比父亲值小时,才会发生交换,所以在预处理建立小根堆的过程中是O(n)的,这样预处理的总复杂度就变成了O(nlogn),删除方面在理论上最坏时间复杂度也是O(nlog^2n)(假设所有的位置都集中在一种子串上,并且“删除堆”和“插入堆”差不多大)

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<queue>
using namespace std;
#define N 1000005
#define LOG 20
int n, n_real, now;
char ss[N];
// 树状数组维护单点删除与单点查询的序列
// 实际坐标->逻辑坐标(删除后的坐标) getsum
// 逻辑坐标->实际坐标  query   树状数组二分
int tra[N];
int getsum(int x)
{int ret=0;for(;x;x-=x&-x) ret+=tra[x];return ret;
}
void update(int x,int k)
{for(;x<=n;x+=x&-x)tra[x]+=k;
}
int query(int k)// 查询删除后序列的第k位置的实际坐标
{int ans=0,sum=0;for(int i=LOG;i>=0;i--){if(ans+(1<<i)<=n && sum+tra[ans+(1<<i)]<k){sum+=tra[ans+(1<<i)];ans+=(1<<i);}}return ans+1;
}
// a是原始数据,tmp是删除后的数组,b表示当前位是否存在(树状数组建立在b上)
bool a[N],tmp[N],b[N];
int pos[N];
void cal_tmp_all()
{int cnt=0;for(int i=1;i<=n;i++){if(b[i]){pos[++cnt]=i;tmp[cnt]=a[i];}}
}
void cal_tmp(int l,int r)
{l=max(1,l);r=min(r,n_real);for(int i=l;i<=r;i++){pos[i]=query(i);tmp[i]=a[pos[i]];}
}
priority_queue<int,vector<int>,greater<int> > S[N],D[N];
//set<int> S[N];
//set<int>::iterator it;
// 将起始点在l r之间,长度为len的数据加入到set或者从set中删除
void update_set(int l,int r,int len,bool flg)
{r=min(n_real,r+len-1);int lim_l= max(now,1<<(len-1)), lim_r= min(n,(1<<len)-1);int mask=(1<<len)-1;int tmp_value=0;for(int i=l;i<=r;i++){tmp_value=((tmp_value<<1)&mask)|tmp[i];if(i-l+1 >= len && tmp_value>=lim_l && tmp_value<=lim_r){if(flg)S[tmp_value].push(pos[i-len+1]);elseD[tmp_value].push(pos[i-len+1]);}}
}
int main()
{scanf("%d",&n);n_real=n;scanf("%s",ss+1);for(int i=1;i<=n;i++){a[i]=int(ss[i]-'0');update(i,1);b[i]=1;}now=1;for(int len=1;n>>(len-1);len++){cal_tmp_all();update_set(1,n_real,len,1);//printf("start len:%d\n",len);for(;now<(1<<len);now++){//printf("now:%d\n",now);if(now>n)return 0;int siz = (int)S[now].size()-(int)D[now].size();if(!siz){printf("-1 0\n");continue;}while(!S[now].empty()&&!D[now].empty() && S[now].top()==D[now].top()){S[now].pop();D[now].pop();}int x=getsum(S[now].top());printf("%d %d\n",x,siz);int l=max(1,x-len+1),r=min(n_real,x+len-1);// 删除受影响的结果cal_tmp(l,r+len-1);update_set(l,r,len,0);// 删除对应的01序列for(int i=x;i<=r;i++){update(pos[i],-1);b[pos[i]]=0;}n_real-=len;// 添加新产生的序列结果cal_tmp(l,x-1+len-1);update_set(l,x-1,len,1);while(!S[now].empty())S[now].pop();while(!D[now].empty())D[now].pop();}}
}

这篇关于[THUPC 2024 初赛] 二进制 (树状数组单点删除+单点查询)(双堆模拟set)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/531557

相关文章

Python列表的创建与删除的操作指南

《Python列表的创建与删除的操作指南》列表(list)是Python中最常用、最灵活的内置数据结构之一,它支持动态扩容、混合类型、嵌套结构,几乎无处不在,但你真的会创建和删除列表吗,本文给大家介绍... 目录一、前言二、列表的创建方式1. 字面量语法(最常用)2. 使用list()构造器3. 列表推导式

MyBatis-Plus逻辑删除实现过程

《MyBatis-Plus逻辑删除实现过程》本文介绍了MyBatis-Plus如何实现逻辑删除功能,包括自动填充字段、配置与实现步骤、常见应用场景,并展示了如何使用remove方法进行逻辑删除,逻辑删... 目录1. 逻辑删除的必要性编程1.1 逻辑删除的定义1.2 逻辑删php除的优点1.3 适用场景2.

MySQL中between and的基本用法、范围查询示例详解

《MySQL中betweenand的基本用法、范围查询示例详解》BETWEENAND操作符在MySQL中用于选择在两个值之间的数据,包括边界值,它支持数值和日期类型,示例展示了如何使用BETWEEN... 目录一、between and语法二、使用示例2.1、betwphpeen and数值查询2.2、be

Java数组动态扩容的实现示例

《Java数组动态扩容的实现示例》本文主要介绍了Java数组动态扩容的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1 问题2 方法3 结语1 问题实现动态的给数组添加元素效果,实现对数组扩容,原始数组使用静态分配

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

MyBatis-Plus使用动态表名分表查询的实现

《MyBatis-Plus使用动态表名分表查询的实现》本文主要介绍了MyBatis-Plus使用动态表名分表查询,主要是动态修改表名的几种常见场景,文中通过示例代码介绍的非常详细,对大家的学习或者工作... 目录1. 引入依赖2. myBATis-plus配置3. TenantContext 类:租户上下文

MySQL基本表查询操作汇总之单表查询+多表操作大全

《MySQL基本表查询操作汇总之单表查询+多表操作大全》本文全面介绍了MySQL单表查询与多表操作的关键技术,包括基本语法、高级查询、表别名使用、多表连接及子查询等,并提供了丰富的实例,感兴趣的朋友跟... 目录一、单表查询整合(一)通用模版展示(二)举例说明(三)注意事项(四)Mapper简单举例简单查询

C#实现插入与删除Word文档目录的完整指南

《C#实现插入与删除Word文档目录的完整指南》在日常的办公自动化或文档处理场景中,Word文档的目录扮演着至关重要的角色,本文将深入探讨如何利用强大的第三方库Spire.Docfor.NET,在C#... 目录Spire.Doc for .NET 库:Word 文档处理利器自动化生成:C# 插入 Word

MySQL中的DELETE删除数据及注意事项

《MySQL中的DELETE删除数据及注意事项》MySQL的DELETE语句是数据库操作中不可或缺的一部分,通过合理使用索引、批量删除、避免全表删除、使用TRUNCATE、使用ORDERBY和LIMI... 目录1. 基本语法单表删除2. 高级用法使用子查询删除删除多表3. 性能优化策略使用索引批量删除避免

MySQL 数据库进阶之SQL 数据操作与子查询操作大全

《MySQL数据库进阶之SQL数据操作与子查询操作大全》本文详细介绍了SQL中的子查询、数据添加(INSERT)、数据修改(UPDATE)和数据删除(DELETE、TRUNCATE、DROP)操作... 目录一、子查询:嵌套在查询中的查询1.1 子查询的基本语法1.2 子查询的实战示例二、数据添加:INSE