高级ST表(区间最长不下降子序列 & 区间GCD

一、区间最长不下降子序列(不一定连续

题面

在这里插入图片描述
在这里插入图片描述

Tips:

  • 子序列没说连续!!
  • Hint:最长上升子序列只需要考虑区间内是否有0,1{0, 1}0,1子序列,考虑维护两个数组LastOne和FirstZero,为了使得该hint含蓄一些,数组含义请自己脑补qwq。最长不下降子序列只有三种情况{000…000},{111…111},{000…111},前两种情况利用前缀和维护即可,第三种情况可以枚举0,1的分界点,总复杂度为O(NM),会超时。考虑用ST表优化这一过程,做到O(1)查询,最终复杂度为O(NlogN+M)。

T两个点代码:

#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
const int N = 1000005;
int n,m,opt,l,r;
int a[N],end0[N][20] = {0},end1[N][20] = {0};
int last1[N][20] = {0},first0[N][20] = {0};
int sum[N] = {0};

void get_inform(){
	int tmp = log(n)/log(2);
	for(int i=1;i<=tmp;i++)
		for(int j=1;j+(1<<i)-1<=n;j++){
			if(last1[j+(1<<(i-1))][i-1] < j+(1<<(i-1))) 
				last1[j][i] = last1[j][i-1];
			else last1[j][i] = max(last1[j][i-1] , last1[j+(1<<(i-1))][i-1]);
			if(first0[j][i-1] >= j+(1<<(i-1))) 
				first0[j][i] = first0[j+(1<<(i-1))][i-1];
			else first0[j][i] = min(first0[j][i-1],first0[j+(1<<(i-1))][i-1]);
			end0[j][i] = end0[j][i-1]+end0[j+(1<<(i-1))][i-1];
			end1[j][i] = max(end0[j][i-1]+end1[j+(1<<(i-1))][i-1]  ,
								end1[j][i-1]+sum[j+(1<<i)-1]-sum[j+(1<<(i-1))-1]);
			
		}
}

int findattack(int L,int R){
	int tmp = log(R-L+1)/log(2);
	int maxx;
	if((1<<tmp) == R-L+1) {
		return max(end0[L][tmp],end1[L][tmp]);
	}
	else maxx = max(end0[L][tmp]+findattack(L+(1<<tmp),R),end1[L][tmp]+sum[R]-sum[L+(1<<tmp)-1]);
	return maxx;
}

int findgreat(int L,int R){
	int tmp = log(R-L+1)/log(2);
	int last ,first;
	if(last1[R-(1<<tmp)+1][tmp] <= R-(1<<tmp)) 
		last = last1[L][tmp];
	else last = max(last1[L][tmp], last1[R-(1<<tmp)+1][tmp]);
	if(first0[L][tmp] >= L+(1<<tmp)) 
		first = first0[R-(1<<tmp)+1][tmp];
	else first = min(first0[L][tmp], first0[R-(1<<tmp)+1][tmp]);
	
	if(first > last) return 1;
	return 2;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		sum[i] = sum[i-1]+a[i];
		if(a[i]==0) {
			last1[i][0] = i-1;
			first0[i][0] = i;
			end0[i][0] = 1;
			end1[i][0] = 0;
		}
		else {
			last1[i][0] = i;
			first0[i][0] = i+1;
			end0[i][0] = 0;
			end1[i][0] = 1;
		}
	}
	get_inform();//last0 & first1 
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&opt,&l,&r);
		if(opt == 1) printf("%d\n",findattack(l,r));
		else printf("%d\n",findgreat(l,r));
	}
}

是考虑用end0end1(两个st表)来维护每个长为2k的区间以0和1结尾的最长子序列长度,这样做的好处是在预处理的时候可以很方便地处理好数据,缺点是在L,R查询的时候需要一定程度的递推([L+(1<<tmp),R]的最长不下降子序列长度不能直接得出来,需要递推)。是从区间相互连接的端点进行考虑的。

T一个点代码:

#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
const int N = 1000005;
int n,m,opt,l,r;
int a[N],st[N][20] = {0};
int last1[N][20] = {0},first0[N][20] = {0};
int sum[N] = {0};

void get_inform(){
	int tmp = log(n)/log(2);
	for(int i=1;i<=tmp;i++)
		for(int j=1;j+(1<<i)-1<=n;j++){
			if(last1[j+(1<<(i-1))][i-1] < j+(1<<(i-1))) 
				last1[j][i] = last1[j][i-1];
			else last1[j][i] = max(last1[j][i-1] , last1[j+(1<<(i-1))][i-1]);
			if(first0[j][i-1] >= j+(1<<(i-1))) 
				first0[j][i] = first0[j+(1<<(i-1))][i-1];
			else first0[j][i] = min(first0[j][i-1],first0[j+(1<<(i-1))][i-1]);
			
			st[j][i] = max(st[j][i-1]+sum[j+(1<<i)-1]-sum[j+(1<<(i-1))-1]  ,
						st[j+(1<<(i-1))][i-1]+(1<<(i-1))-(sum[j+(1<<(i-1))-1]-sum[j-1]));
			
		}
}

int findattack(int L,int R){
	int tmp = log(R-L+1)/log(2);
	int maxx;
	maxx = max(st[L][tmp]+sum[R]-sum[L+(1<<tmp)-1] , 
				st[R-(1<<tmp)+1][tmp]+(R-(1<<tmp)-L+1)-(sum[R-(1<<tmp)]-sum[L-1]));
	return maxx;
}

int findgreat(int L,int R){
	int tmp = log(R-L+1)/log(2);
	int last ,first;
	if(last1[R-(1<<tmp)+1][tmp] <= R-(1<<tmp)) 
		last = last1[L][tmp];
	else last = max(last1[L][tmp], last1[R-(1<<tmp)+1][tmp]);
	if(first0[L][tmp] >= L+(1<<tmp)) 
		first = first0[R-(1<<tmp)+1][tmp];
	else first = min(first0[L][tmp], first0[R-(1<<tmp)+1][tmp]);
	
	if(first > last) return 1;
	return 2;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		sum[i] = sum[i-1]+a[i];
		if(a[i]==0) {
			last1[i][0] = i-1;
			first0[i][0] = i;
		}
		else {
			last1[i][0] = i;
			first0[i][0] = i+1;
		}
		st[i][0] = 1;
	}
	get_inform();//last0 & first1 
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&opt,&l,&r);
		if(opt == 1) printf("%d\n",findattack(l,r));
		else printf("%d\n",findgreat(l,r));
	}
}

昨天半夜想到的……闭上眼睛开始睡觉,突然意识到应该怎样O(1)查询:st[i][j]存的是[i,i+(1<<j)-1]区间最长不下降子序列长度,在合并或者查询的时候,对于大的区间分成两部分(可能有重叠区域)则0和1的分界点一定属于其中某一个区间,所以用max(st[左区间]+右边1数,st[右区间+左边0数])可以完美考虑到所有的情况。是从0,1分界点的角度出发进行考虑的。
可惜还是T掉了一个点,咋整呢
经过一些测试证明是常数大了,要减小常数

AC代码:

#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
const int N = 1000005;
int n,m,opt,l,r;
int a[N],st[N][20] = {0};
int last1[N][20] = {0},first0[N][20] = {0};
int sum[N] = {0};

void get_inform(){
	int tmp = log(n)/log(2);
	for(int i=1;i<=tmp;i++)
		for(int j=1;j+(1<<i)-1<=n;j++){
			if(last1[j+(1<<(i-1))][i-1] < j+(1<<(i-1))) 
				last1[j][i] = last1[j][i-1];
			else last1[j][i] = max(last1[j][i-1] , last1[j+(1<<(i-1))][i-1]);
			if(first0[j][i-1] >= j+(1<<(i-1))) 
				first0[j][i] = first0[j+(1<<(i-1))][i-1];
			else first0[j][i] = min(first0[j][i-1],first0[j+(1<<(i-1))][i-1]);
			
			st[j][i] = max(st[j][i-1]+sum[j+(1<<i)-1]-sum[j+(1<<(i-1))-1]  ,
						st[j+(1<<(i-1))][i-1]+(1<<(i-1))-(sum[j+(1<<(i-1))-1]-sum[j-1]));
			
		}
}

void findattack(){
	int tmp = log(r-l+1)/log(2);
	int maxx = max(st[l][tmp]+sum[r]-sum[l+(1<<tmp)-1] , 
				st[r-(1<<tmp)+1][tmp]+(r-(1<<tmp)-l+1)-(sum[r-(1<<tmp)]-sum[l-1])); //声明时赋值应该会更快 
	printf("%d\n",maxx);
}

void findgreat(){
	int tmp = log(r-l+1)/log(2);
	int last ,first;
	if(last1[r-(1<<tmp)+1][tmp] <= r-(1<<tmp)) 
		last = last1[l][tmp];
	else last = max(last1[l][tmp], last1[r-(1<<tmp)+1][tmp]);
	if(first0[l][tmp] >= l+(1<<tmp)) 
		first = first0[r-(1<<tmp)+1][tmp];
	else first = min(first0[l][tmp], first0[r-(1<<tmp)+1][tmp]);

	if(first > last) printf("1\n");
	else printf("2\n");
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		sum[i] = sum[i-1]+a[i];
		if(a[i]==0) {
			last1[i][0] = i-1;
			first0[i][0] = i;

		}
		else {
			last1[i][0] = i;
			first0[i][0] = i+1;
		}
		st[i][0] = 1;
	}
	get_inform();//last0 & first1 
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&opt,&l,&r);
		if(opt == 1) findattack();//函数传参全部删了,还改成了void类型直接输出,减少参数传来传去的复杂度 
		else findgreat();
	}
	return 0;
}

修改点:

  • 可以声明时赋值的都声明即赋值了
  • 查询时用到的函数全部变成了void类型,直接在内部输出,进去的参数也删掉了,全部用全局变量,减少了传参的复杂度,竟然过了hhh

二、区间GCD

题面

在这里插入图片描述

GCD怎么用st表呢?因为GCD是可以合并的啊……数学不好一时间没想出来……
害 下面放代码

AC代码:

#include<iostream>
#include<cmath>
using namespace std;
const int N = 500005;
int n,m,L,R;
int a[N] = {0};
int st[N][20] = {0};

int gcd(int x,int y){
	int r = x%y;
	while(r){
		x = y;
		y = r;
		r = x%y;
	}
	return y;
}

void getst(){
	int tmp = log(n)/log(2);
	for(int i=1;i<=tmp;i++){
		for(int j=1;j+(1<<i)-1<=n;j++){
			st[j][i] = gcd(st[j][i-1],st[j+(1<<(i-1))][i-1]);
		}
	}
	return ;
}

int binary1(int l,int r,int target){//找最大值 
	int mid;
	while(l<=r){
		mid = (l+r)>>1;
		int tmp = log(mid-L+1)/log(2);
		if(gcd(st[L][tmp],st[mid-(1<<tmp)+1][tmp]) >= target)
			l = mid+1;
		else r = mid-1;
	}
	return l-1;
}

int binary2(int l,int r,int target){//找最小值 
	int mid;
	while(l<=r){
		mid = (l+r)>>1;
		int tmp = log(mid-L+1)/log(2);
		if(gcd(st[L][tmp],st[mid-(1<<tmp)+1][tmp]) <= target)
			r = mid-1;
		else l = mid+1;
	}
	return r+1;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		st[i][0] = a[i];
	} 
	getst();
	for(int i=1;i<=m;i++){
		scanf("%d%d",&L,&R);
		int tmp = log(R-L+1)/log(2);
		int ans = gcd(st[L][tmp],st[R-(1<<tmp)+1][tmp]);
		printf("%d %d\n",ans,binary1(L,n,ans)-binary2(L,n,ans));
	}
	return 0; 
} 

思路

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值