校内模拟赛T5:连续的“包含”子串长度( nekameleoni?) —— 线段树单点修改,区间查询 + 尺取法合并
nekameleoni
区间查询和修改
给定N,K,M(N个整数序列,范围1~K,M次查询或修改)
如果是修改,则输入三个数,第一个数为1代表修改,第二个数为将N个数中第i个数做修改,第三个数为修改成这个数(例如1 3 5就是修改数组中第3个数,使之变为5)
如果是查询,则输入一个数2,查询N个数中包含1~K每一个数的最短连续子序列的长度
输入
第一行包含整数N、K和M(1 ≤ N,M ≤ 100000,1 ≤ K ≤ 50)
第二行输入N个数,用空格隔开,组成该数组
然后M行表示查询或修改
若是1 p v(1 ≤ p ≤ N,1 ≤ v ≤ K)
若是2则是询问1~K的最短连续子序列长度。
输出
输出必须包含查询的答案,如果不存在则输出-1。分数分布
对于30%的数据:1≤M,N≤5000。样例输入1
4 3 5
2 3 1 2
2
1 3 3
2
1 1 1
2
样例输出1
3
-1
4样例输入2
6 3 6
1 2 3 2 1 1
2
1 2 1
2
1 4 1
1 6 2
2
样例输出2
3
3
4
解释一下题意,如果询问为2则输出包含1-k的最长连续序列,否则进行单点修改。
单点修改,区间查询,明显可以使用线段树,但如何进行区间的维护却是此题的难点。
考虑使用3个量来维护线段树的每一个节点:
1.前缀中,用\(x_{i}\)表示含\(x_{i}\)种不同数字的连续的序列。存储每个\(x_{i}\)的状态(也就是哪\(x_{i}\)个不同的数字,这里可以使用二进制压50位来实现)。
对于多个前缀,如果它们包含的不同的数字相同,我们只需要存储长度最小的那一个,这样可以保证结果最优。
同理后缀也可以这么实现。
2.ans,表示在当前节点表示的区间中,包含\(k\)个不同的数的最短区间的长度,也就是最终要输出的答案。这样的好处是我们可以直接输出根节点的\(ans\)作为答案。
有了前缀和后缀,我们就可以进行合并了。对于节点\(t\)的左儿子\(l\)和右儿子\(r\),当我们要将\(l\)和\(r\)合并成\(t\)时,只需要将\(l\)的后缀和\(r\)的前缀进行合并,得到满足条件的最小值,将它与\(l\)的ans,和\(r\)的ans进行比较,最优的值便可以最为\(t\)的ans。
前缀和后缀的维护比较复杂,这些细节的问题我会在代码中的注释解释(比如二进制的使用,还有前缀和后缀的更新操作都很复杂。)
这里探讨的主要是前缀和后缀的合并操作。
设\(l\)的后缀有\(k_{1}\)个,\(r\)的后缀有\(k_{2}\)个,我们可以考虑暴力枚举每一个\(k_{1}\)和\(k_{2}\)来合并,但这样做时间复杂度过高,所以便可以使用尺取法。
我们可以发现\(l\)若的第\(i\)个后缀与\(r\)的第\(j\)个后缀合并后满足有\(k\)个不同的数字,那么便无需考虑\(j\)之后的后缀。
这便是尺取法的精髓思想。如果你还不懂尺取法,可以去网上找博客看看(或许我以后会写相关的博客?)。
尺取法的时间复杂度是线性的,所以我们整个程序的时间复杂度是\(O(n_{logn}k)\)
代码
#include <iostream>
#include <cstdio>
using namespace std;
#define N 100010
#define MAXN 262144
#define T 131071
#define LL long long
#define inf 0x7f7f7f7f
typedef pair<LL,int> P;
struct Fake {
P suf[52],pre[52];
int ans,sum;
Fake():ans(inf) {};
}Fuck[MAXN];
int n,k,m;
void Merge(int t,int l, int r) {
int ans=inf;
Fuck[t].ans=min(Fuck[l].ans,Fuck[r].ans);//维护ans
Fuck[t].sum=0;
int lenpre=0,lensuf=0;
for(int i=0;i<Fuck[l].sum;i++)
Fuck[t].pre[lenpre++]=Fuck[l].pre[i];//将左儿子的前缀继承到当前节点的前缀
for(int i=0;i<Fuck[r].sum;i++) {
if(lenpre==0 ||( Fuck[r].pre[i].first & Fuck[t].pre[lenpre-1].first) != Fuck[r].pre[i].first) {//如果右节点的前缀包含的不同数数量比左儿子的前缀数量还多,则可以
Fuck[t].pre[lenpre]=Fuck[r].pre[i];//添加进来
if(lenpre>0) Fuck[t].pre[lenpre].first|=Fuck[t].pre[lenpre-1].first;
lenpre++;
}
}
for(int i=0;i<Fuck[r].sum;i++)//后缀处理同理
Fuck[t].suf[lensuf++]=Fuck[r].suf[i];
for(int i=0;i<Fuck[l].sum;i++) {
if(lensuf==0 ||( Fuck[l].suf[i].first & Fuck[t].suf[lensuf-1].first) != Fuck[l].suf[i].first) {
Fuck[t].suf[lensuf]=Fuck[l].suf[i];
if(lensuf>0) Fuck[t].suf[lensuf].first|=Fuck[t].suf[lensuf-1].first;
lensuf++;
}
}
Fuck[t].sum=lenpre;
int j=0;
for(int i=Fuck[l].sum-1;i>=0;i--) {//尺取法合并操作
while(j<Fuck[r].sum) {
if((Fuck[r].pre[j].first | Fuck[l].suf[i].first) == ((1ll<<k)-1))
break;
j++;
}
if(j<Fuck[r].sum) Fuck[t].ans=min(Fuck[t].ans,Fuck[r].pre[j].second - Fuck[l].suf[i].second+1);
}
}
void update(int pos,int x) {
int now=T+pos;
Fuck[now].suf[0]= make_pair(1ll<<(x-1),pos) ;//用一个pair类型来表示一个前缀
Fuck[now].pre[0]= make_pair(1ll<<(x-1),pos) ;//first为一个二进制数,表示包含了哪些
Fuck[now].ans=inf;//不同的数字,pos为对应的下标,用于计算答案
Fuck[now].sum=1;//sum统计相应的前后缀数量
while(now/2) {//循环型线段树,从儿子往根节点操作
now/=2;
Merge(now,now<<1,now<<1|1);
}
}
int main() {
cin>>n>>k>>m;
for(int i=1,A;i<=n;i++) {
cin>>A;
update(i,A);
}
for(int i=1,t,pos,x;i<=m;i++) {
cin>>t;
if(t==2) {
if(Fuck[1].ans==inf) cout<<"-1"<<endl;
else cout<<Fuck[1].ans<<endl;
}
else {
cin>>pos>>x;
update(pos,x);
}
}
return 0;
}
感受
做了这道题,算是让我接受了线段树和二进制压位运算的洗礼吧。线段树真的是十分灵活的数据结果呀。
校内模拟赛T5:连续的“包含”子串长度( nekameleoni?) —— 线段树单点修改,区间查询 + 尺取法合并的更多相关文章
- 模拟赛 怨灵退治 题解(Hall定理+线段树)
题意: 有 n 群怨灵排成一排,燐每秒钟会选择一段区间,消灭至多 k 只怨灵. 如果怨灵数量不足 k,则会消灭尽量多的怨灵. 燐作为一只有特点的猫,它选择的区间是不会相互包含的.它想要知道它每秒最多能 ...
- 【2019.7.26 NOIP模拟赛 T3】化学反应(reaction)(线段树优化建图+Tarjan缩点+拓扑排序)
题意转化 考虑我们对于每一对激活关系建一条有向边,则对于每一个点,其答案就是其所能到达的点数. 于是,这个问题就被我们搬到了图上,成了一个图论题. 优化建图 考虑我们每次需要将一个区间向一个区间连边. ...
- 【2019.10.7 CCF-CSP-2019模拟赛 T2】绝对值(abs)(线段树细节题)
找规律 设\(p_i=a_{i+1}-a_i\),则答案就是\(\sum_{i=1}^{n-1}p_i\). 考虑若将\(a_i\)加上\(x\)(边界情况特殊考虑),就相当于是将\(p_{i-1}\ ...
- 【CSP模拟赛】天才绅士少女助手克里斯蒂娜(线段树&读入优化&输出优化)
题面描述 红莉栖想要弄清楚楼下天王寺大叔的显像管电视对“电话微波炉(暂定)”的影响.选取显像管的任意一个平面,一开始平面内有个n电子,初始速度分别为vi,定义飘升系数为 $$\sum_{1\leqsl ...
- 【BZOJ1396】识别子串 - 后缀自动机+线段树
题意: Description Input 一行,一个由小写字母组成的字符串S,长度不超过10^5 Output L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. 题解: ...
- 【20170521校内模拟赛】热爱生活的小Z
学长FallDream所出的模拟赛,个人感觉题目难度还是比较适中的,难度在提高+左右,可能比较接近弱省省选,总体来讲试题考查范围较广,个人认为还是很不错的. 所有试题如无特殊声明,开启-O2优化,时限 ...
- BZOJ1396 识别子串 字符串 SAM 线段树
原文链接http://www.cnblogs.com/zhouzhendong/p/9004467.html 题目传送门 - BZOJ1396 题意 给定一个字符串$s$,$|s|\leq 10^5$ ...
- BZOJ 1396: 识别子串( 后缀数组 + 线段树 )
这道题各位大神好像都是用后缀自动机做的?.....蒟蒻就秀秀智商写一写后缀数组解法..... 求出Height数组后, 我们枚举每一位当做子串的开头. 如上图(x, y是height值), Heigh ...
- BZOJ 1396&&2865 识别子串[后缀自动机 线段树]
Description 在这个问题中,给定一个字符串S,与一个整数K,定义S的子串T=S(i, j)是关于第K位的识别子串,满足以下两个条件: 1.i≤K≤j. 2.子串T只在S中出现过一次. 例如, ...
随机推荐
- 移动端1像素解决方法,根据媒体查询transform缩放
.borderOnePx{ position: relative; } .borderOnePx::after { content: ''; height:1px; background:#000; ...
- java 注解 Annontation
什么是注解? 对于很多初次接触的开发者来说应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解.它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metada ...
- python学习shutil模块的文件压缩和解压用法
shutil模块可以创建压缩包并返回文件路径,例如 zip,tar,下面详细其用法 base_name 压缩包的文件名,也可以是压缩包的路径,只是文件名时,则保存至当前目录,否则保存指定路径 data ...
- MySQL Server类型的MySQL 客户端的下载、安装和使用
mysql server 5.5的下载 下载地址:https://dev.mysql.com/downloads/mysql/5.0.html#linux mysql server 5.5的安装 双击 ...
- document.body.scrollTop值为0的解决方法[转]
做页面的时候可能会用到位置固定的层,读取document.body.scrollTop来设置层的位置,像这样, window.onscroll=function () { ...
- xxPay搭建步骤
随着电子商务的发展,移动支付也走进我们的日常生活,就像很多基础业务,如物流.云平台一样,我们现在做什么都离不开她.正因为如此,支付是当下最活跃最前沿的话题,很多流行的技术最早用来实现支付,很多新的业务 ...
- Sql Server 显示插入Identity字段
先像下面这样写 SET IDENTITY_INSERT [表名] ON ...INSERT INTO ... 别忘了在插入完数据后再执行下面的语句 SET IDENTITY_INSERT [表 ...
- ZYNQ系列
赛灵思公司(Xilinx)推出的行业第一个可扩展处理平台Zynq系列.旨在为视频监视.汽车驾驶员辅助以及工厂自动化等高端嵌入式应用提供所需的处理与计算性能水平. 中文名 ZYNQ系列 开发商 赛灵 ...
- Swagger详解(SpringBoot+Swagger集成)(转)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/ai_miracle/article/de ...
- maven 提取jar包 依赖及打包排除
<properties> <project.targetDir>D:\jar</project.targetDir> <project.targetServe ...