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. SCUT - 38 - 屠场的秘密 - 分解

    https://scut.online/p/38 要求是2016的倍数,把每个数分解成有2016的倍数和余数,两数余数的乘积是2016的倍数,则原数的乘积也是2016的倍数.

  2. socket服务器

    Python 3.x,已经改名为socketserver:Python 2. #coding=utf-8 #1.必须自己创建一个请求处理类,并且这个类要继承BaseRequesHandler,并且还要 ...

  3. ECarts 的初步使用

    ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等) ...

  4. Django 的 logging日志文件配置

    在Django的settings配置文件里配置以下信息: import os BASE_LOG_DIR = os.path.join(BASE_DIR , "log") # log ...

  5. 1-基于Xilinx XCKU115的半高PCIe x8 硬件加速卡

    基于Xilinx XCKU115的半高PCIe x8 硬件加速卡 一.概述 本板卡系我公司自主研发,采用Xilinx公司的XCKU115-3-FLVF1924-E芯片作为主处理器,主要用于FPGA硬件 ...

  6. 解决SVN异常 cleanup failed

    winndows上偶尔使用SVN的时候就会整出一些有的没的问题,比如"cleanup failed to process the following paths previous opera ...

  7. 初学Java 二维数组找出最近的两个点

    import java.util.Scanner; public class FindNearestPoints { public static void main(String[] args) { ...

  8. ARM Linux BenchMark

    http://tonyho.github.io/ARM%20Linux%20BenchMark.html 1.背景说明 许多公司有很多不同的ARM SoC的研发产品,ARM核心可能有Cortex-A8 ...

  9. css3 动画实例

    animation 动画 animation-duration 代码实例: <!DOCTYPE html> <html> <head> <meta chars ...

  10. JS原型和原型链(3)

    构造函数创建对象: function Person() { } var person = new Person(); person.name = 'Kevin'; console.log(person ...