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?) —— 线段树单点修改,区间查询 + 尺取法合并的更多相关文章

  1. 模拟赛 怨灵退治 题解(Hall定理+线段树)

    题意: 有 n 群怨灵排成一排,燐每秒钟会选择一段区间,消灭至多 k 只怨灵. 如果怨灵数量不足 k,则会消灭尽量多的怨灵. 燐作为一只有特点的猫,它选择的区间是不会相互包含的.它想要知道它每秒最多能 ...

  2. 【2019.7.26 NOIP模拟赛 T3】化学反应(reaction)(线段树优化建图+Tarjan缩点+拓扑排序)

    题意转化 考虑我们对于每一对激活关系建一条有向边,则对于每一个点,其答案就是其所能到达的点数. 于是,这个问题就被我们搬到了图上,成了一个图论题. 优化建图 考虑我们每次需要将一个区间向一个区间连边. ...

  3. 【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}\ ...

  4. 【CSP模拟赛】天才绅士少女助手克里斯蒂娜(线段树&读入优化&输出优化)

    题面描述 红莉栖想要弄清楚楼下天王寺大叔的显像管电视对“电话微波炉(暂定)”的影响.选取显像管的任意一个平面,一开始平面内有个n电子,初始速度分别为vi,定义飘升系数为 $$\sum_{1\leqsl ...

  5. 【BZOJ1396】识别子串 - 后缀自动机+线段树

    题意: Description Input 一行,一个由小写字母组成的字符串S,长度不超过10^5 Output L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. 题解: ...

  6. 【20170521校内模拟赛】热爱生活的小Z

    学长FallDream所出的模拟赛,个人感觉题目难度还是比较适中的,难度在提高+左右,可能比较接近弱省省选,总体来讲试题考查范围较广,个人认为还是很不错的. 所有试题如无特殊声明,开启-O2优化,时限 ...

  7. BZOJ1396 识别子串 字符串 SAM 线段树

    原文链接http://www.cnblogs.com/zhouzhendong/p/9004467.html 题目传送门 - BZOJ1396 题意 给定一个字符串$s$,$|s|\leq 10^5$ ...

  8. BZOJ 1396: 识别子串( 后缀数组 + 线段树 )

    这道题各位大神好像都是用后缀自动机做的?.....蒟蒻就秀秀智商写一写后缀数组解法..... 求出Height数组后, 我们枚举每一位当做子串的开头. 如上图(x, y是height值), Heigh ...

  9. BZOJ 1396&&2865 识别子串[后缀自动机 线段树]

    Description 在这个问题中,给定一个字符串S,与一个整数K,定义S的子串T=S(i, j)是关于第K位的识别子串,满足以下两个条件: 1.i≤K≤j. 2.子串T只在S中出现过一次. 例如, ...

随机推荐

  1. [LOJ 3101] [Luogu 5332] [JSOI2019]精准预测(2-SAT+拓扑排序+bitset)

    [LOJ 3101] [Luogu 5332] [JSOI2019]精准预测(2-SAT+拓扑排序+bitset) 题面 题面较长,略 分析 首先,发现火星人只有死和活两种状态,考虑2-SAT 建图 ...

  2. [常用类]Math、Random、System、BigInteger、BigDecimal

    Math类中的成员全是静态成员,构造方法是 私有的,以避免被创建对象 常用方法: int abs() double ceil() //向上取整 double floor() //向下取整 int ma ...

  3. #10017 传送带(SCOI 2010)(三分套三分)

    [题目描述] 在一个 2 维平面上有两条传送带,每一条传送带可以看成是一条线段.两条传送带分别为线段 AB 和线段 CD.lxhgww 在 AB上的移动速度为 P ,在 CD 上的移动速度为 Q,在平 ...

  4. js的事件流理解

    面试问到js的事件流,当时说的不是很清楚,现在觉得有必要把这个弄清楚. 事件捕获和事件冒泡 事件流描述的是从页面中接收事件的顺序,也可理解为事件在页面中传播的顺序. 事件流主要分为两种,即事件捕获和事 ...

  5. Vue —— You may use special comments to disable some warnings. Use // eslint-disable-next-line to ignore the next line. Use /* eslint-disable */ to ignore all warnings in a file.问题

    方法1: 在build/webpack.base.conf.js文件中,找到module->rules中有关eslint的规则,注释或者删除掉就可以了 module: { rules: [ // ...

  6. 采用pacemaker+corosync实现postgresql双机热备、高可用方案

    环境说明 参照上章已完成postgresql流复制配置,并关闭postgres服务. su - postgres pg_ctl -D /data/postgresql/data/ stop -m fa ...

  7. ideamaven版的MBG逆向工程

    一.简介 简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及bean类. 支持基本的增删改查,以及QBC风格的条件查询. 但是表连接.存储 ...

  8. nodejs npm资料

    安装淘宝的 cnpm : npm install --global cnpm 不想安装 cnpm  又想使用淘宝的服务器来下载 : npm install jquery --registry=http ...

  9. 如何从mysql备份中提取单张表数据

    1.先提取备份数据中的前50行出来,查看一下备份数据格式    head -50 bakdb.sql > head50.txt        类似下面的数据是我们所需要提取的:        / ...

  10. Codeforces Round #425 (Div. 2) - B

    题目链接:http://codeforces.com/contest/832/problem/B 题意:给定一个好字母集合(只有小写字母,除了这些外其余都是坏字母集合),给定一个匹配模式串, 模式串只 ...