校内模拟赛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中出现过一次. 例如, ...
随机推荐
- Python之文件和异常IO
文件和异常 读写文本文件 读取文本文件时,需要在使用open函数时指定好带路径的文件名(可以使用相对路径或绝对路径)并将文件模式设置为'r'(如果不指定,默认值也是'r'),然后通过encoding参 ...
- POJ-2528 Mayor's posters (离散化, 线段树)
题目传送门: POJ-2528 题意就是在一个高度固定的墙面上贴高度相同宽度不同的海报,问贴到最后还能看到几张?本质上是线段树区间更新问题,但是要注意的是题中所给数据范围庞大,直接搞肯定会搞出问题,所 ...
- python实现不同条件下单据体的颜色不一样,比如直接成本分析表中关闭的细目显示为黄色
#引入clr运行库 import clr #添加对cloud插件开发的常用组件的引用 clr.AddReference('Kingdee.BOS') clr.AddReference('Kingdee ...
- HTML创建链接框
使用CSS样式创建一个漂亮的链接框吧 <!DOCTYPE html> <html> <head> <style> a:link,a:visited { ...
- 2019-5-28-VisualStudio-扩展开发
title author date CreateTime categories VisualStudio 扩展开发 lindexi 2019-05-28 19:51:49 +0800 2018-2-1 ...
- Object中有哪些公用方法?
clone()方法 实现对象的浅复制,只有实现了Cloneable接口才能调用该方法. toString()方法 返回该对象的字符串表示. equals()方法: 在Object中与“==”的定义是一 ...
- 获取服务进程server.exe的pid(0号崩溃)
#include "stdafx.h" #include <windows.h> #include <iostream> #include <COMD ...
- 【学习】008 Socket网络编程
课程目标 网络模型 TCP协议与UDP协议区别 Http协议底层实现原理. 什么是网络模型 网络编程的本质是两个设备之间的数据交换,当然,在计算机网络中,设备主要指计算机.数据传递本身没有多大的难度, ...
- 030:spaceless和autoescape 标签
1.spaceless 标签: spaceless 标签:移除html标签中的空白字符.包括空格.tab键.换行等.示例代码如下: {% spaceless %} <p> <a hr ...
- ht-5 treemap特性
(1)TreeMap类通过使用红黑树实现Map接口 (2)TreeMap提供按排序顺序存储键值对的有效手段,同时允许快速检索 (3)不同于散列映射,树映射保证它的元素按键的自然顺序升序排列 (4)Tr ...