高手专项训练-RMQ问题

A. 数列求和(1s,256MiB)

【题目描述】

定义一个数列的价值为:数列中最大的一个数减去最小的一个数(即该数列的极差)。

例如:数列 3 , 1 , 7 , 2 3,1,7,2 3,1,7,2 的价值为 6 = 7 − 1 6=7-1 6=71;数列 42 , 42 42,42 42,42 价值为 0 = 42 − 42 0=42-42 0=4242

现在给你一个数列,要你求出所有连续子数列的价值总和。

【输入格式】

第一行为 n n n,表示数列的长度。

接下来 n n n 行,每行为一个不大于 1 0 8 10^8 108 的正整数,表示数列中的每个元素。

【输出格式】

输出所有连续子序列的总和。

【样例】
【输入样例】
3
1
2
3
【输出样例】
4

想法1

每一个数都可能成为一个区间的最大值和最小值对答案产生贡献,用两个单调栈维护一下每个数成为区间最大值(最小值)的左右范围,最后统一计算答案即可。

时间复杂度 O ( n ) O(n) O(n)。(考试的时候一直在想st表,丝毫没有想到可以用单调栈…

【代码1】(单调栈写法)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[300001],n,l[300001],r[300001],ll[300001],rr[300001];
int st[300001],top;
long long ans;
int main(){
	//freopen("sequence.in","r",stdin);
	//freopen("sequence.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		r[i]=rr[i]=n+1;
	}
    for(int i=1;i<=n;++i){
    	while(top&&a[st[top]]>a[i])
    	    r[st[top--]]=i;
    	l[i]=st[top];
    	st[++top]=i;
	}
	memset(st,0,sizeof(st));
	top=0;
	for(int i=1;i<=n;++i){
		while(top&&a[st[top]]<a[i])
		    rr[st[top--]]=i;
		ll[i]=st[top];
		st[++top]=i;
	}
	for(int i=1;i<=n;++i){
		ans-=1ll*(i-l[i])*(r[i]-i)*a[i];
		ans+=1ll*(rr[i]-i)*(i-ll[i])*a[i];
	}
	printf("%lld\n",ans);
	return 0;
}

想法2

使用st表维护区间最大值和最小值.

记函数 s o l v e ( l , r ) solve(l,r) solve(l,r) 表示计算区间 [ l , r ] [l,r] [l,r] 的所有子区间的最大值贡献。记 p p p 为区间 [ l , r ] [l,r] [l,r] 的最大值所在位置,则所有跨过 p p p 的区间的最大值均为 a p a_{p} ap,故最大值 a p a_{p} ap 的贡献为 ( p − l + 1 ) ( r − p + 1 ) a p (p−l+1)(r−p+1)a_p (pl+1)(rp+1)ap。接着递归调用 s o l v e ( l , p − 1 ) solve(l,p−1) solve(l,p1), s o l v e ( p + 1 , r ) solve(p+1,r) solve(p+1,r),计算其余区间的贡献即可。

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

【代码2】(st表写法)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    return x*f; 
}
int n,m;
int f_max[300011][32];
int f_min[300011][32];
int i_max[300011][32];
int i_min[300011][32];
int lg[1000001];
int a[300011];
long long ans;
int rm_max(int x,int y){
    int k=lg[y-x+1];
    if(f_max[x][k]>f_max[y-(1<<k)+1][k])return i_max[x][k];
    else return i_max[y-(1<<k)+1][k];
}
int rm_min(int x,int y){
    int k=lg[y-x+1];
    if(f_min[x][k]<f_min[y-(1<<k)+1][k])return i_min[x][k];
    else return i_min[y-(1<<k)+1][k];
}
void solve_max(int l,int r){
    if(l>r)return;
    int p=rm_max(l,r);
    ans+=1ll*a[p]*(p-l+1)*(r-p+1);
    solve_max(l,p-1);
    solve_max(p+1,r);
}
void solve_min(int l,int r){
    if(l>r)return;
    int p=rm_min(l,r);
    ans-=1ll*a[p]*(p-l+1)*(r-p+1);
    solve_min(l,p-1);
    solve_min(p+1,r);
}
int main(){
    //freopen("sequence.in","r",stdin);
    //freopen("sequence.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=f_max[i][0]=f_min[i][0]=read();
    for(int i=1;i<=n;i++)
        i_min[i][0]=i_max[i][0]=i;
    for(int i=2;i<=n;i++)
        lg[i]=lg[i>>1]+1;
    for(int l=1;l<=lg[n];l++){
        for(int i=1;i+(l<<1)-1<=n;i++){
            if(f_max[i][l-1]>f_max[i+(1<<(l-1))][l-1]){
                f_max[i][l]=f_max[i][l-1];
                i_max[i][l]=i_max[i][l-1];
            }
            else{
                f_max[i][l]=f_max[i+(1<<(l-1))][l-1];
                i_max[i][l]=i_max[i+(1<<(l-1))][l-1];
            }
            if(f_min[i][l-1]<f_min[i+(1<<(l-1))][l-1]){//转移最大(小)值的下标
                f_min[i][l]=f_min[i][l-1];
                i_min[i][l]=i_min[i][l-1];
            }
            else{
                f_min[i][l]=f_min[i+(1<<(l-1))][l-1];
                i_min[i][l]=i_min[i+(1<<(l-1))][l-1];
            }
        }
    }
    solve_max(1,n);
    solve_min(1,n);
    cout<<ans<<endl;
}

【想法3】(不怎么会写,学习中)

想法2中分治过程的分治树即为笛卡尔树,将原序列的笛卡尔树建出后计算,时间复杂度 O ( n ) O(n) O(n)

### 回答1: 假设要将N变为Fibonacci,设当前最接近N的两个Fibonacci为a和b,且a < b。 则有以下两种情况: 1. N在a和b之间 此时我们只需要通过一步操作将N变成a或b一个即可。具体来说,若N与a的差值小于N与b的差值,则进行-1操作,否则进行+1操作。 2. N小于a 此时我们需要将N不断+1,直到它变成a或者a+1。因为Fibonacci数列单调递增,所以接下来只需要考虑如何将a或a+1变成N即可。 3. N大于b 与2类似,我们需要将N不断-1,直到它变成b或者b-1。然后将b或b-1变成N即可。 综上所述,我们只需要预处理出Fibonacci数列,然后根据N与数列相邻的两个的大小关系,选择不同的操作即可。最后统计操作次即可得到答案。 ### 回答2: 要将一个给定的N转变为Fibonacci,可以采用动态规划的方法求解。首先,我们可以通过计算得出前N个Fibonacci数列,然后比较与N的差值来确定最小。 具体步骤如下: 1. 我们可以先生成Fibonacci数列,直到最大值超过N。生成的方法是从1和1开始,每次将前两个相加得到下一个。 2. 通过遍历Fibonacci数列,找出与N最接近的两个,记为fib[i]和fib[i+1],且fib[i] <= N <= fib[i+1]。 3. 若N等于fib[i],则不需要任何操作,步为0;否则,我们可以通过不断进行加1或减1操作逼近N。 4. 定义一个变量step来记录步,初始值设为0。接下来,我们需要从fib[i]逼近到N,可以进行以下操作: a) 若N > fib[i],则将当前增加1,即N = N + 1,步加1,step = step + 1。 b) 若N < fib[i],则将当前减少1,即N = N - 1,步加1,step = step + 1。 c) 若N = fib[i],则我们已经成功将N变为Fibonacci,无需再进行操作,步不变。 5. 重复步骤4,直到N = fib[i],此时得到的步即为最小。 总结:通过动态规划的方法,我们可以找到最小的步将N转化为Fibonacci。 ### 回答3: 如果想将一个N变成一个Fibonacci,我们可以先从1开始,依次计算出Fibonacci数列的每一个,直到找到大于或等于N的。然后我们可以比较N和这个与N之间的差值,取两者之间较小的作为目标差值。 接下来,我们可以使用1或-1操作来逐渐逼近目标差值,每次操作都使N与目标差值之间的距离变小。具体的操作方法是: 1. 如果目标差值为正,则每次将N减去1。 2. 如果目标差值为负,则每次将N加上1。 3. 当N与目标差值相等时,停止操作。 这样,在每个操作步骤,我们都使N与目标差值之间的绝对值减少1,直到N与目标差值相等为止。因此,最少需要的步就是N与目标差值的绝对值。 总结起来,要将一个N变成一个Fibonacci,最少需要的步就是N与离N最近的Fibonacci之间的差值的绝对值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值