题目描述
区间查询和修改
给定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” - change the value of the pth number into v (1 ≤ p ≤ N,1 ≤ v ≤ K)
• “2” - what is the length of the shortest contiguous subarray of the array containing all the integers from 1 to K
输出格式
输出必须包含查询的答案,如果不存在则输出-1
样例
样例输入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
思路详解
好题啊!!!(毒瘤,恶心。。。)
初步讨论
不用多说,看到区间马上想到线段树,而包含1到k每个数的最小子序列是个什么玩意儿???
接下来就要用尺取法了,想象两个指针curL和curR,不满足条件就将curR在区间上右移(扩大子序列),满足条件后再将curL向右移(缩小子序列),最后就可以得出答案(这个方法十分适用于连续区间和子序列之类的)
在线段树上我们维护的区间应包含什么呢??
1. 首先肯定是答案,即最小子序列
2. 其次该想如何求出它,就会想到求这个区间的前缀和后缀(前后缀长度相同)
3. 有答案(最小子序列),就该有这段子序列包含数的情况
4. 前后缀的长度是必须要有的
对于询问操作,就直接输出,而每一次更新就直接进行单点修改
而对于包含数的状态就可以压位(压成二进制),如包含数5和1,就表示为 66 ,2的5次方 加 2 的一次方
(ll就是long long)定义pair<ll,int>存储,其 first 存状态,second 存数的下标(在数组中的下标)
struct node {
pair<ll,int> fro[55] , bac[55] ;//fro前缀,bac后缀
int len , tip ;//len表示包含k个数的最小子序列长度,tip表示前后缀的长度
node() { len = MAXN ; }//初始化
}Tree[MAXN];//zkw线段树
具体操作
在更改后,不断向上更新父亲节点(个人用的zkw线段树),更新后,就会有合并操作
合并操作中,我们要将节点 x 得到其左儿子(ls)的前缀,右儿子(rs)的后缀,在考虑将节点 x 的前后缀进行加长,在其 ls 和
其 rs 的前后缀上合并加长
for( int i = 0 ; i < Tree[ls].tip ; ++ i ) {//得到左儿子的前缀
Tree[x].fro[lenf] = Tree[ls].fro[i] ;
lenf ++ ;//注意结束后会多出一位,所以后面要减1
}//lenf即x节点前缀长度
for( int i = 0 ; i < Tree[rs].tip ; ++ i ) {
if( lenf == 0 || ( Tree[rs].fro[i].first&Tree[x].fro[lenf-1].first ) != Tree[rs].fro[i].first ) {//不包含,不重复
Tree[x].fro[lenf] = Tree[rs].fro[i] ;//加长前缀
if( lenf > 0 )
Tree[x].fro[lenf].first |= Tree[x].fro[lenf-1].first ;
lenf ++ ;
}
}
后缀也是同样的做法,详见代码啦
然后就是尺取法求出节点 x 的最小子序列了
int curR = 0 ;
while( curL >= 0 ) {//尺取法
while( curR < Tree[rs].tip ) {
if( ( Tree[rs].fro[curR].first | Tree[ls].bac[curL].first ) == (1ll<<K)-1 )
break ;//全包含
++ curR ;
}
if( curR < Tree[rs].tip )
Tree[x].len = min( Tree[x].len , Tree[rs].fro[curR].second-Tree[ls].bac[curL].second+1 );//更新答案
curL -- ;
}
ls 的后缀区间到 rs 前缀区间找最小子序列
这道就差不多,超恶心
总结
线段树维护,每次用尺取法求出最小子序列,状态压位一下
真心很难(其实打裸的尺取法就可以拿 70 分,骗分超爽)
不看题解是遭不住的
代码
好题(毒瘤的一比)一道,消化理解。。。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#define MAXN 262144
#define ll long long
using namespace std;
int N , M , K ;
ll L ;
struct node {
pair<ll,int> fro[55] , bac[55] ;//fro前缀,bac后缀
int len , tip ;//len表示包含k个数的最小子序列长度,tip表示前后缀的长度
node() { len = MAXN ; }
}Tree[MAXN];//zkw线段树
void change( int x , int ls , int rs ) {//ls左儿子,rs右儿子
Tree[x].len = MAXN ;
Tree[x].tip = 0 ;
int lenf = 0 , lenb = 0 ;
for( int i = 0 ; i < Tree[ls].tip ; ++ i ) {//得到左儿子的前缀
Tree[x].fro[lenf] = Tree[ls].fro[i] ;
lenf ++ ;//注意结束后会多出一位,所以后面要减1
}
for( int i = 0 ; i < Tree[rs].tip ; ++ i ) {
if( lenf == 0 || ( Tree[rs].fro[i].first&Tree[x].fro[lenf-1].first ) != Tree[rs].fro[i].first ) {
Tree[x].fro[lenf] = Tree[rs].fro[i] ;//加长前缀
if( lenf > 0 )
Tree[x].fro[lenf].first |= Tree[x].fro[lenf-1].first ;
lenf ++ ;
}
}
for( int i = 0 ; i < Tree[rs].tip ; ++ i ) {//得到后缀,同前缀
Tree[x].bac[lenb] = Tree[rs].bac[i] ;
lenb ++ ;
}
for( int i = 0 ; i < Tree[ls].tip ; ++ i ) {
if( lenb == 0 || ( Tree[ls].bac[i].first&Tree[x].bac[lenb-1].first ) != Tree[ls].bac[i].first ) {
Tree[x].bac[lenb] = Tree[ls].bac[i] ;
if( lenb > 0 )
Tree[x].bac[lenb].first |= Tree[x].bac[lenb-1].first ;
lenb ++ ;
}
}
Tree[x].len = min( Tree[ls].len , Tree[rs].len );
Tree[x].tip = lenf ;
int curR = 0 , curL = Tree[ls].tip-1 ;
while( curL >= 0 ) {//尺取法
while( curR < Tree[rs].tip ) {
if( ( Tree[rs].fro[curR].first | Tree[ls].bac[curL].first ) == (1ll<<K)-1 )
break ;
++ curR ;
}
if( curR < Tree[rs].tip )
Tree[x].len = min( Tree[x].len , Tree[rs].fro[curR].second-Tree[ls].bac[curL].second+1 );//更新答案
curL -- ;
}
}
void update( int loc , int num ) {//单点更新
loc += L ;
Tree[loc].len = MAXN ;
Tree[loc].tip = 1 ;
Tree[loc].fro[0] = make_pair( 1ll<<num , loc-L );//更新前缀,压位
Tree[loc].bac[0] = make_pair( 1ll<<num , loc-L );//更新后缀,压位
while( loc/2 ) {//向上更新父亲
loc /= 2 ;
change( loc , loc*2 , loc*2+1 );
}
}
void build() {
L = 1 ;
while( L < N+2 )
L *= 2 ;
}
int main() {
scanf("%d%d%d", &N , &K , &M );
build();
for( int i = 1 ; i <= N ; ++ i ) {//初始化
int a ;
scanf("%d", &a );
update(i,a-1) ;
}
for( int i = 1 ; i <= M ; ++ i ) {
int how ;
scanf("%d", &how );
if( how == 1 ) {//修改
int a , b ;
scanf("%d%d", &a , &b );
update( a , b-1 );//减1方便后面压位
}
else {//查询
if( Tree[1].len != MAXN )
printf("%d\n", Tree[1].len );
else printf("-1\n");
}
}
}