描述
区间查询和修改
给定 N , K , M N,K,M N,K,M( N N N个整数序列,范围 1 K 1~K 1 K, M M M次查询或修改)
如果是修改,则输入三个数,第一个数为1代表修改,第二个数为将N个数中第i个数做修改,第三个数为修改成这个数(例如1 3 5就是修改数组中第3个数,使之变为5)
如果是查询,则输入一个数2,查询N个数中包含1~K每一个数的最短连续子序列的长度
输入
第一行包含整数 N 、 K N、K N、K和 M M M
第二行输入N个数,用空格隔开,组成该数组
然后M行表示查询或修改
• “1 p v” - change the value of the pth number into v
• “2” - what is the length of the shortest contiguous subarray of the array containing all the integers from 1 to K
输出
输出必须包含查询的答案,如果不存在则输出-1
范围
1 ≤ N , M ≤ 100000 , 1 ≤ K ≤ 50 , 1 ≤ p ≤ N , 1 ≤ v ≤ K 1 ≤ N,M ≤ 100000,1 ≤ K ≤ 50,1 ≤ p ≤ N,1 ≤ v ≤ K 1≤N,M≤100000,1≤K≤50,1≤p≤N,1≤v≤K
样例
样例输入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
既然都看见这种修改和查询的操作了,第一反应肯定就是线段树或者莫队,显而易见,此题不用莫队,因为中间有修改操作(也有可能是我太弱,不知道某些高端操作)
线段树中我们就维护包含K个数的序列的最小长度,当然,这东西一看就知道不好维护,我们需要借助另一些东西。想想,这个最小长度肯定是一个区间,那么如何从左右儿子当中维护出这样一个区间呢,那便是左儿子的所有后缀序列和右儿子的所有前缀序列,这样一来,当前节点的所有区间就都可以表示出来了。若当前节点是左儿子,那我们就需要它的后缀,而它的后缀其实最开始是来源于它的右儿子,所以对于每一个区间,我们都需要存储它的前缀序列和后缀序列
如果用数组,肯定爆炸,看一看数据范围 k ≤ 50 k \leq 50 k≤50,那肯定压缩状态呀,这个数二进制位上第 i i i位为 1 1 1即表示数 i i i在此序列中出现过,
对于全部序列,肯定有些序列的状态是相同的(如112,12),我们肯定要取长度最小的,那么如何比较状态相同呢?用 & \& &符号,因为 & \& &是先把两个数转换为二进制,进行比较,两位同时为1那么值才为1,其他情况均为0,所以当前状态与原数组中的状态 & \& &一下,若等于当前状态,说明两个序列在本质上是相同的,不用更新,而因为 k ≤ 50 k \leq 50 k≤50,所以序列最多的个数也不会超过50,而考虑到右儿子的前缀是从中间开始的,所以我们要将右儿子的前缀与前面的连起来(状态继承),两个序列 | 一下就行了
当前区间的ans肯定要先在左右儿子的ans中取最小值,而因为左右区间结合了,所以我们还要检查一下中间的序列会不会有更好的答案,这时候,就要用到尺取法了,在尺取的时候,你会发现有个问题怎么把这个序列的长度搞出来,所以,我们在存储序列的状态时,顺手就把序列结束的位置存进去,尺取的时候就可以更新答案了
由于递归线段树更新会耗费大量时间,所以我们选择从叶节点自底向上更新,就减少了递归的时间,那么如何找到此节点在线段树中的位置呢?我们就把从这个线段树看做一个完全二叉树,那么它的叶节点就有 n n n个,那么它的层数就有 l o g n log n logn层,而除去最后一层,上面总共的节点数就有 2 l o g n − 1 2^{logn}-1 2logn−1个,而叶节点的位置也就是 i + 2 l o g n − 1 i+2^{logn}-1 i+2logn−1了
#include
#include
#include
#include
#include
#include
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define IL inline
#define Pr pair
#define M 100005
int offet;
struct node{
Pr pre[55], sub[55];//一个前缀,一个后缀,pair中一个状态,一个序列的结束位置
int v_sum;//序列的个数
int ans;
node(){ ans = INF; }
}T[M*2+70000];
int n, k, m, q, u, v;
LL goal;
IL int qkp(int x, int y){//计算上面节点的总数
int sum = 1;
while( y ){
if( y&1 )
sum = sum*x;
x *= x;
y >>= 1;
}
return sum;
}
IL void calc(int x, int l, int r){
T[x].ans = INF;
T[x].v_sum = 0;
int lenpre = 0, lensub = 0;
for(int i = 0; i < T[l].v_sum; ++ i)//此区间的前缀是先从左儿子的前缀开始的
T[x].pre[lenpre++] = T[l].pre[i];
for(int i = 0; i < T[r].v_sum; ++ i){//右儿子的前缀需要练起来
if( !lenpre || (T[r].pre[i].first&T[x].pre[lenpre-1].first) != T[r].pre[i].first ){//若状态不同
T[x].pre[lenpre] = T[r].pre[i];
if( lenpre > 0 ){
T[x].pre[lenpre].first |= T[x].pre[lenpre-1].first;//状态继承
}
lenpre++;
}
}
for(int i = 0; i < T[r].v_sum; ++ i)//此区间的后缀是先从右儿子的后缀开始的
T[x].sub[lensub++] = T[r].sub[i];
for(int i = 0; i < T[l].v_sum; ++ i){
if( !lensub || (T[l].sub[i].first&T[x].sub[lensub-1].first) != T[l].sub[i].first ){//状态不同
T[x].sub[lensub] = T[l].sub[i];
if( lensub > 0 ){
T[x].sub[lensub].first |= T[x].sub[lensub-1].first;//状态继承
}
lensub++;
}
}
T[x].ans = min(T[l].ans, T[r].ans);
T[x].v_sum = lenpre;
int cl = T[l].v_sum-1, cr = 0;
LL SS = 0;
while( cl >= 0 && cr < T[r].v_sum ){//尺取(有毒)
while( cr < T[r].v_sum ){
SS = T[l].sub[cl].first|T[r].pre[cr].first;
if( SS >= goal )
break;
cr++;
}
while( cr < T[r].v_sum && SS >= goal && cl >= 0 ){
T[x].ans = min(T[x].ans, T[r].pre[cr].second-T[l].sub[cl].second+1);
cl--;
SS = T[l].sub[cl].first|T[r].pre[cr].first;
}
}
}
IL void update(int i, int val){
i += offet;//计算出在线段树中的位置
T[i].pre[0] = Pr(1ll<<val, i-offet);//更新本节点状态
T[i].sub[0] = Pr(1ll<<val, i-offet);
T[i].ans = INF;
T[i].v_sum = 1;
while( i>>1 ){//自底向上更新
i >>= 1;
calc(i, i<<1, i<<1|1);
}
}
int main(){
scanf("%d%d%d", &n, &k, &m);
goal = (1ll<<k)-1;
offet = qkp(2, ceil(log(double(n))/log(2.0)))-1;
for(int i = 1; i <= n; ++ i){
scanf("%d", &u);
update(i, u-1);
}
for(int i = 1; i <= m; ++ i){
scanf("%d", &q);
if( q == 2 ){
if( T[1].ans != INF )
printf("%d\n", T[1].ans);
else printf("-1\n");
}
else if( q==1 ) {
scanf("%d%d", &u, &v);
update(u, v-1);
}
}
}