C++解题报告:连续的“包含”子串长度——(线段树+尺取法)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_44013342/article/details/97234657

题目描述

区间查询和修改

给定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"); 
		}
	}
} 

​

猜你喜欢

转载自blog.csdn.net/qq_44013342/article/details/97234657
今日推荐