首页 > 其他分享 >Living-Dream 系列笔记 第66期

Living-Dream 系列笔记 第66期

时间:2024-07-26 16:57:30浏览次数:12  
标签:Living last r2 int max st 66 l1 Dream

RMQ 问题 / ST 表:静态区间求最值。

实现(以最大值为例):

  • 倍增 dp,预处理 \(st_{i,j}\) 表示区间 \([i,i+2^j-1]\) 内的最大值,我们有转移方程:

    \[st_{i,j}=\max(st_{i,j-1},st_{i+2^{j-1},j-1}) \]

    相当于是把 \([i,i+2^{j-1}-1]\) 与 \([i+2^{j-1},i+2^j-1]\) 这两段区间的最大值拼了起来。

    倍增中常把 \(2^j\) 拆分为 \(2^{j-1}+2^{j-1}\)

  • 对于每组询问 \([l,r]\),找到大于等于区间长度一半的二次幂(实际上易证这就是 \(\log_2 \ r-l+1\)),从 \(l,r\) 分别找,从而覆盖 \([l,r]\)。

    \[\begin{cases} j=\log_2 r-l+1\\ ans=\max(dp_{l,j},dp_{r-2^j+1,r}) \end{cases} \]

P3865

板子。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e5+5,M=31;
int n,m,a[N];
int dp[N][M];

void STinit(){
	for(int i=1;i<=n;i++)
		dp[i][0]=a[i];
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
} 
int STquery(int l,int r){
	int t=log(r-l+1)/log(2);
	return max(dp[l][t],dp[r-(1<<t)+1][t]);
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	STinit(); 
	while(m--){
		int l,r; cin>>l>>r; cout<<STquery(l,r)<<'\n';
	}
	return 0;
}

CF689D

首先发掘性质,\(\max^{r}_{i=l} b_i\) 显然单调不减, \(\min^{r}_{i=l} a_i\) 显然单调不增,这说明可以只枚举起点,二分终点,从而确定区间。

接着我们发现两者相等的必定为一段连续区间,于是我们就二分查找满足 \(\max^{r}_{i=l} b_i \le \min^{r}_{i=l} a_i\) 的最大的 \(l1\),以及满足 \(\max^{r}_{i=l} b_i \ge \min^{r}_{i=l} a_i\) 的最大的 \(r2\),这个 \(l\) 对答案的贡献即为 \(l1-r2+1\)。

注意最后要在判断一次 \(\max^{r}_{i=l} b_i = \min^{r}_{i=l} a_i\),因为有可能找不到 \(l1,r2\)。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=2e5+5,M=31; 
int n,m,ans; 
int a[N],b[N]; 
int dpa[N][M],dpb[N][M]; 
 
void STinitA(){ 
	for(int i=1;i<=n;i++) 
		dpa[i][0]=a[i]; 
	for(int j=1;(1<<j)<=n;j++) 
		for(int i=1;i+(1<<j)-1<=n;i++) 
			dpa[i][j]=max(dpa[i][j-1],dpa[i+(1<<(j-1))][j-1]); 
} 
void STinitB(){ 
	for(int i=1;i<=n;i++) 
		dpb[i][0]=b[i]; 
	for(int j=1;(1<<j)<=n;j++) 
		for(int i=1;i+(1<<j)-1<=n;i++) 
			dpb[i][j]=min(dpb[i][j-1],dpb[i+(1<<(j-1))][j-1]); 
} 
int STqueryA(int l,int r){ 
	int t=log(r-l+1)/log(2); 
	return max(dpa[l][t],dpa[r-(1<<t)+1][t]); 
} 
int STqueryB(int l,int r){ 
	int t=log(r-l+1)/log(2); 
	return min(dpb[l][t],dpb[r-(1<<t)+1][t]); 
} 
 
signed main(){ 
	ios::sync_with_stdio(0); 
	cin.tie(0),cout.tie(0); 
	cin>>n; 
	for(int i=1;i<=n;i++) cin>>a[i]; 
	for(int i=1;i<=n;i++) cin>>b[i]; 
	STinitA(),STinitB();  
	for(int i=1;i<=n;i++){ 
		int l1=i-1,r1=n+1; 
		int l2=i-1,r2=n+1; 
		while(l1+1<r1){ 
			int mid=(l1+r1)>>1; 
			if(STqueryA(i,mid)<=STqueryB(i,mid)) 
				l1=mid; 
			else 
				r1=mid; 
		} 
		while(l2+1<r2){ 
			int mid=(l2+r2)>>1; 
			if(STqueryA(i,mid)>=STqueryB(i,mid)) 
				r2=mid; 
			else 
				l2=mid; 
		} 
		if(STqueryA(i,l1)==STqueryB(i,l1)&&STqueryA(i,r2)==STqueryB(i,r2)) ans+=l1-r2+1; 
	} 
	cout<<ans; 
	return 0; 
}

LOJ #10121

首先,容易发现一条性质:若有一个确定的最长完美序列 \([i,j]\),则一定不存在完美序列 \([k,j]\),其中 \(k\) 满足 \(k<i\)。这说明完美序列存在单调性。

然后对于一组询问 \([l,r]\),考虑进行分割,哪部分可以更好算出贡献,就以它进行分割

在这里,我们发现可能存在一个点 \(p\),使得以区间 \([l,p)\) 内的点为右端点的完美序列的左端点 \(L_1\) 满足 \(L_1 < l\),而以区间 \([p,r]\) 内的点为右端点的完美序列的左端点 \(L_2\) 满足 \(l \le L_2 \le r\)。

因为单调性的存在,所以 \(p\) 可以二分查找得到。

计算 \([l,p)\) 内的贡献是简单的,即为 \((p-1)-L_1+1=p-L_1\)。

对于每个 \(i \in [p,r]\),它贡献为 \(i-L_2+1\),用 ST 表维护这个式子的 \(\max\) 即可。

于是 \([l,r]\) 中的最长完美序列的长度即为 \(\max(p-L_1,\operatorname{query}(p,r))\)(\(\operatorname{query}\) 为 ST 表中的查询)。

接下来就是 \(L_1,L_2\) 的维护。

令 \(last_{a_i}\) 表示 \(a_i\) 上一次出现的位置,\(st_i\) 表示以 \(i\) 为右端点的最长完美序列的左端点。

显然有转移:

\[st_i = \max(st_{i-1},last_i+1) \]

(可以继承 \(st_{i-1}\) 是因为有单调性)

最后注意下标从 \(0\) 开始,以及 \(a_i\) 可能为负,\(last_{a_i}\) 要用 \(\operatorname{map}\) 或者平移也行。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=2e5+5,M=31;
int n,m,a[N];
int dp[N][M];
int st[N];
int l,r;
map<int,int> last;

void STinit(){
	for(int i=1;i<=n;i++)
		dp[i][0]=i-st[i]+1;
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
int STquery(int l,int r){
	int t=log(r-l+1)/log(2);
	return max(dp[l][t],dp[r-(1<<t)+1][t]);
}
int bin(int l,int r){
	int lt=l-1,rt=r+1;
	while(lt+1<rt){
		int mid=(lt+rt)>>1;
		if(st[mid]<l) lt=mid;
		else rt=mid;
	}
	return rt;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++){
		st[i]=max(st[i-1],last[a[i]]+1); 
		last[a[i]]=i;
	}
	STinit();
	while(m--){
		//int l,r; 
		cin>>l>>r;
		l++,r++;
		int p=bin(l,r);
		if(p>r) cout<<r-l+1<<'\n';
		else cout<<max(p-l,STquery(p,r))<<'\n';
	}
	//cout<<ans;
	return 0;
}

CF1142B

对于 \([l,r]\) 中的每个数 \(i\),我们都尝试将其作为 \(p\) 的循环移位的终点,

从 \(i\) 不断往前跳 \(n-1\) 次,记录它在 \(a_i\) 能跳到的点 \(b_i\),

把 \(b_i\) 扔进 ST 表中,对于每组询问 \([l,r]\) 取 \(\max\),

若这个值 \(\ge l\),则说明存在,否则不存在。

具体实现细节见 code。

code
#include<bits/stdc++.h>
using namespace std;

const int N=2e5+5,M=31;
int n,m,q;
int p[N],a[N];
int pos[N],last[N],b[N],st[N][M];

void STinit1(){
	//memset(st,0,sizeof st);
	for(int j=1;j<=25;j++)
		for(int i=1;i<=m;i++)
			st[i][j]=st[st[i][j-1]][j-1];
}
void STinit2(){
	memset(st,0,sizeof st);
	for(int i=1;i<=m;i++) st[i][0]=b[i];
	for(int j=1;(1<<j)<=m;j++)
		for(int i=1;i+(1<<j)-1<=m;i++)
			st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
int STjump(int x){
	int now=0;
	for(int i=25;i>=0;i--){
		if(now+(1<<i)<n)
			x=st[x][i],
			now+=(1<<i);
	}
	return x;
}
int STquery(int l,int r){
	int t=log2(r-l+1);
	return max(st[l][t],st[r-(1<<t)+1][t]);
}

int main(){
    //last[x]表示x在a中的下标
    //pos[x]表示x在p中的下标
    //st[i][j]表示i跳回2^j步所到的点
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++)
		cin>>p[i],pos[p[i]]=i;
	for(int i=1;i<=m;i++){
		cin>>a[i];
		if(pos[a[i]]==1) st[i][0]=last[p[n]];
		else st[i][0]=last[p[pos[a[i]]-1]]; 
		last[a[i]]=i;
	}
	STinit1();
	for(int i=1;i<=m;i++)
		b[i]=STjump(i);
	STinit2();
	while(q--){
		int l,r; cin>>l>>r;
		cout<<(STquery(l,r)>=l?1:0);
	}
	return 0;
}

标签:Living,last,r2,int,max,st,66,l1,Dream
From: https://www.cnblogs.com/XOF-0-0/p/18325731

相关文章

  • steam休闲游戏推荐:《Mimpi Dreams》《Pizza Possum》《洞窟物语》
    对于想要放松娱乐的时刻,Steam平台上有一些非常受欢迎的休闲游戏,这里推荐三款:1、《Mimpi Dreams》《Mimpi Dreams》中文名为《米皮大冒险:梦境》,是一款由 Silicon Jelly 开发的冒险解谜游戏。该游戏的故事承接前作,小狗米皮在成功找到主人并一同回家后,某天夜晚受到梦魇的......
  • Living-Dream 系列笔记 第65期
    HDU6567首先我们发现每棵树内部的距离已经固定,只有经过新边的路径才会产生贡献。又因为重心到树上所有节点的距离和最小,所以我们连接两树重心。然后我们想到一个经典套路:计算距离可以不枚举点,只枚举边。于是我们枚举每条边,计算出它们各自被经过的次数,再求和即为答案。维护\(......
  • 警告:tensorflow:模型是用形状构建的(无、66、200、3)
    我有以下代码生成有关形状的错误:fromkeras.layersimportDense,ActivationfromkerasimportSequentialfromkeras.modelsimportload_modelfromtensorflow.keras.optimizersimportAdamimporttensorflowimportkerasfromtensorflow.python.keras.layersimpor......
  • Living-Dream 系列笔记 第64期
    树的重心当\(u\)作为根时,其节点数最大的子树最小,则称\(u\)为树的重心。性质一:节点数最大的子树的节点数不超过\(\frac{节点总数}{2}\)。(反证法,若某重心\(u\)的节点数最大的子树的节点数超过\(\frac{节点总数}{2}\),则将其一个子节点提起来会更优)性质二:至多两个且一......
  • Living-Dream 系列笔记 第63期
    树的中心当选取树上节点\(u\)为根时,最长链最短,则称\(u\)为树的中心。性质一:至多\(2\)个且一定相邻。性质二:一定位于树的直径上。性质三:若一棵树有多条直径,则它们必定交于树的中心。性质四:树的中心为根时,根到直径两端点分别为最长/次长链。U392706板子。......
  • 24暑假算法刷题 | Day21 | LeetCode 669. 修剪二叉搜索树,108. 将有序数组转换为二叉搜
    目录669.修剪二叉搜索树题目描述题解108.将有序数组转换为二叉搜索树题目描述题解538.把二叉搜索树转换为累加树题目描述题解669.修剪二叉搜索树点此跳转题目链接题目描述给你二叉搜索树的根节点root,同时给定最小边界low和最大边界high。通过修剪二叉......
  • Living-Dream 系列笔记 第62期
    树的直径:定义:树上两个距离最远的点形成的简单路径(不重复走一条边/点)性质:不唯一。树的直径的端点一定是度数为\(1\)的点。证明:显然。树的直径若有多条,则必交汇于一点,即中心。证明:首先每条直径只能交于端点(因为是一棵树,一个节点不能有两个父节点),然后此交点必定......
  • P6610 [Code+#7] 同余方程(二次剩余)
    题意给定\(p,x\),求满足\(a^2+b^2\equivx\pmodp\)的解的组数,保证\(p\)为若干奇素数的乘积且\(\mu(p)\not=0\)。\(n\le10^5,p\le10^7\)。前置知识二次剩余综合题。首先二次剩余有一个重要的符号勒让德符号:\(\left(\dfrac{a}{p}\right)\),这个东西在当\(a\)在模\(......
  • 实现登录验证,有3次机会,如果用户名为“丁真”,密码为“666”提示登录成功
    1importjava.util.Scanner;2publicclassexercise06{3//编写一个main方法4publicstaticvoidmain(String[]args){56//实现登录验证,有3次机会,如果用户名为“丁真”,密码为“666”提示登录成功,7//否则提示还有几次机会,请使用f......
  • 【嵌入式DIY实例-ESP8266篇】-LCD ST7789显示DHT11传感器数据
    LCDST7789显示DHT11传感器数据文章目录LCDST7789显示DHT11传感器数据1、硬件准备与接线2、代码实现本文介绍如何将ESP8266NodeMCU板(ESP-12E)与DHT11(RHT01)数字湿度和温度传感器连接。NodeMCU从DHT11传感器读取温度和湿度值,并在ST7789TFT......