RMQ问题

RMQ问题

单调队列

单调队列

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

const int N=1e6+5;
int n,m,a[N],q[N];
int main()
{
	scanf("%d %d",&n,&m);
	
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	
	int head=1,tail=0;
	for(int i=1;i<m;++i)
	{
		while(head<=tail&&a[i]<=a[q[tail]]) tail--;
		q[++tail]=i;
	}
	
	for(int i=m;i<=n;++i)
	{
		if(head<=tail&&i-q[head]>=m) head++;
		while(head<=tail&&a[i]<=a[q[tail]]) tail--;
		q[++tail]=i;
		
		printf("%d ",a[q[head]]);
	}
	
	printf("\n");
	
	head=1,tail=0;
	for(int i=1;i<m;++i)
	{
		while(head<=tail&&a[i]>=a[q[tail]]) tail--;
		q[++tail]=i;
	}
	
	for(int i=m;i<=n;++i)
	{
		if(head<=tail&&i-q[head]>=m) head++;
		while(head<=tail&&a[i]>=a[q[tail]]) tail--;
		q[++tail]=i;
		
		
		printf("%d ",a[q[head]]);
	}
}

最大连续和


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

const int N=3e6+5;
ll n,a[N],b[N],s[N],q[N<<1];
bool vis[N];

int main()
{
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i]>>b[i],s[i]=s[i+n]=a[i]-b[i];
	for(int i=1;i<=n*2;++i) s[i]+=s[i-1];
	
	int head=1,tail=0;
	for(int i=n*2;i>=0;--i)
	{
		if(head<=tail&&q[head]-i>n+1) head++;
		
		if(i<n&&head<=tail&&s[q[head]]-s[i]>0) vis[i+1]=1;
		
		while(head<=tail&&s[i]<=s[q[tail]]) tail--;
		q[++tail]=i;
	}
	
	for(int i=1;i<=n;++i) s[i]=s[i+n]=a[i]-b[i];
	for(int i=n*2;i>=0;++i) s[i]+=s[i+1];
	
	
	head=1,tail=0;
	for(int i=1;i<=n*2+1;++i)
	{
		if(head<=tail&&i-q[head]>n+1) head++;
		
		if(i>n+1&&head<=tail&&s[q[head]]-s[i]>0) vis[i-n]=1;
		
		while(head<=tail&&s[i]<=s[q[tail]]) tail--;
		q[++tail]=i;
	}
	
	
	for(int i=1;i<=n;++i)
	if(vis[i]) printf("TAK\n");
	else printf("NIE\n");
}

修剪草坪


#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const int N=1e5+5;

int n,m;
ll f[N],sum[N];
int q[N];
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> sum[i];
        sum[i] += sum[i - 1];
    }

    int head = 0, tail = 0;
    for(int i = 1; i <= n; i++){
        if(i - q[head] > m && head <= tail)head++;
        //不选第 i 个, 选第 i 个
        f[i] = max(f[i - 1], f[q[head] - 1] - sum[q[head]] + sum[i]);
        //维护f[i - x - 1] - s[i - x] 的单调递减行, 则队头的元素一定是最大的
        while(head <= tail && f[q[tail] - 1]  - sum[q[tail]] <= f[i - 1] - sum[i])tail--;
        q[++tail] = i;
    }
    cout << f[n] << endl;
    return 0;
}

最敏捷的机器人

【题目描述】Wind 设计了很多机器人。但是它们都认为自己是最强的,于是,一场比赛开始了……

机器人们都想知道谁是最敏捷的,于是它们进行了如下一个比赛。首先,他们面前会有一排共 n 个数,它们比赛看谁能最先把每连续 k 个数中最大和最小值写下来,当然,这些机器人运算速度都很快,它们比赛的是谁写得快。

但是 Wind 也想知道答案,你能帮助他吗?

【数据输入】第一行为 n,k,意义如题目描述。

第二行共 n 个数,为数字序列,所有数字均在 int 范围内,即所有数均为整数,且在 [−231,231−1]范围内。

【数据输出】共 n−k+1 行,第 i 行为第 i 至第 i+k−1 这 k 个数中的最大和最小值。

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

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

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,k,a[N],q1[N],q2[N];
int main()
{
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
     
    int l1=1,l2=1,r1=0,r2=0;
    for(int i=1;i<k;++i)
    {
        while(l1<=r1&&a[i]>=a[q1[r1]]) r1--;
        while(l2<=r2&&a[i]<=a[q2[r2]]) r2--;
         
        q1[++r1]=q2[++r2]=i;
    }
     
    for(int i=k;i<=n;++i)
    {
        if(l1<=r1&&q1[l1]+k-1<i) l1++;
        if(r2<=r2&&q2[l2]+k-1<i) l2++;
         
        while(l1<=r1&&a[i]>=a[q1[r1]]) r1--;
        while(l2<=r2&&a[i]<=a[q2[r2]]) r2--;
        q1[++r1]=q2[++r2]=i;
        printf("%d %d\n",a[q1[l1]],a[q2[l2]]);
    }
}

旅行问题


设:\(sum[i]=∑a[1—i]-b[1—i]\),则有\(sum[j]-sum[i-1]\)为从\(i\)\(j\)的花费剩余,如果\(i\)可以走到\(i+n\),则必定有\(sum[i—i+n]-sum[i-1]>0\)。而当\(min(sum[i—i+n])-sum[i-1]>0\)时,该条件一定成立,故单调队列就只需要维护\(i—i+n\)的最小值

#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
typedef long long ll;
int st[N], l, r;
int vis[N];
ll s[N];
int a[N], b[N];
int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++)
	{
        cin >> a[i] >> b[i];
        s[i] = s[i + n] = a[i] - b[i];  //得到俩个点之间的权值
    }
    
    for(int i = 1; i <= 2 * n; i++) s[i] += s[i - 1];
    
    l = 0;r = -1;
    
    for(int i = 2 * n; i >= 0; i--)
	{  //顺时针走,需要倒着求
        if(st[l] >= i + n) l++;  //维护n个车站
        
        if(i < n)
		{   
            if(s[st[l]]-s[i]>=0) vis[i + 1] = 1;  //起点i + 1与 n 个车站内的最小前缀和进行比较, 判断是否可达 
        }

        while(l <= r && s[i] <= s[st[r]]) r--;
        st[++r] = i;
    }
    //翻转一下,逆时针方向走
    b[0] = b[n];
    for(int i = 1; i <= n; i++) s[i] = s[i + n] = a[i] - b[i - 1];
    for(int i = 1; i <= 2 * n; i++) s[i] += s[i - 1];
    l = 0;
    r = -1;
    for(int i = 1; i <= 2 * n; i++)
	{
        if(st[l] <= i - n)l++; 
        if(i > n)
		{
            if(s[i] >= s[st[l]])vis[i - n] = 1; 
        }
        while(l <= r && s[st[r]] <= s[i])r--;
        st[++r] = i;
    }
    for(int i = 1; i <= n; i++)
    if(vis[i])printf("TAK\n");
    else printf("NIE\n");
    
    return 0;
}

【NOIP2011提】选择客栈(Day 1)

【题目描述】丽江河边有n家很有特色的客栈,客栈按照其位置顺序从1到n编号。每家客栈都按照某一种色调进行装饰(总共k种,用整数0~k-1表示),且每家客栈都设有一家咖啡店,每家咖啡店均有各自的最低消费。
两位游客一起去丽江旅游,他们喜欢相同的色调,又想尝试两个不同的客栈,因此决定分别住在色调相同的两家客栈中。晚上,他们打算选择一家咖啡店喝咖啡,要求咖啡店位于两人住的两家客栈之间(包括他们住的客栈),且咖啡店的最低消费不超过p。
他们想知道总共有多少种选择住宿的方案,保证晚上可以找到一家最低消费不超过p元的咖啡店小聚。

【数据输入】每组输入数据共n+1行。
第一行三个整数n,k,p,每两个整数之间用一个空格隔开,分别表示客栈的个数,色调的数目和能接受的最低消费的最高值;
接下来的n行,第i+1行两个整数,之间用一个空格隔开,分别表示i号客栈的装饰色调和i号客栈的咖啡店的最低消费。

【数据输出】每组输出只有一行,一个整数,表示可选的住宿方案的总数。

【样例输入】 5 2 3
0 5
1 3
0 2
1 4
1 5

【样例输出】 3

2人要住同样色调的客栈,所有可选的住宿方案包括:住客栈①③,②④,②⑤,④⑤,但是若选择住4、5号客栈的话,4、5号客栈之间的咖啡店的最低消费是4,而两人能承受的最低消费是3元,所以不满足要求。因此只有前3种方案可选。

【数据规模】 对于30%的数据,有n≤100;
对于50%的数据,有n≤1,000;
对于100%的数据,有2≤n≤200,000,0<k≤50,0≤p≤100,0≤最低消费≤100。

#include<bits/stdc++.h>
using namespace std;
#define N 200005
int n,m,ans,p,pos;
int sum[N],pre[N],col[55];

int main()
{
	scanf("%d %d %d",&n,&m,&p);
	
	for(int i=1,x,y;i<=n;++i)
	{
		scanf("%d %d",&x,&y);
		++x;
		
		pre[i]=col[x];
		col[x]=i;
		sum[i]=sum[pre[i]]+1;
		
		if(y<=p) pos=i;
		
		for(int j=pre[i];j;j=pre[j])
		if(j<=pos)
		{
			ans+=sum[j];
			break;
		}
	}
	
	printf("%d",ans);
}

ST算法

对于问题(RMQ,Random Maximum Query):给你一串固定、不修改的数字,询问多次某个区间内的最大值或者最小值。

用朴素的想法,依次遍历得到答案,时间复杂度为O(n),但是对于多次询问,如1E6次询问,这样的算法就不是最优的了。所以出现了ST算法。

ST算法的原理是动态规划,通过O(logn)的预处理dp数组,得到O(1)的询问。

1.预处理

假设dp[i,j]表示从a[i]到a[i+2j-1]这个范围内的最大值,即以a[i]为起点连续2j个数的最大值。

我们把它一分为二,每段元素都为2(j-1)个数字。也就是说dp[i,j]分为dp[i, j - 1]和f[i + 2j-1, j- 1]两部分(自己代入一下dp的定义)。

所以我们很容易得到状态转一方程\(dp[i][j] = max(dp[i][j - 1], dp[i + 2^(j-1)][j - 1])\);

我们在给\(dp[i][0]\)初始化一下均为a[i]。预处理完毕。

2.询问
先引入一个k值,每个区间均为2(k-1)个数字,所以两部分区间为[i, 2(k-1) + i -1]和[j - 2(k-1)+ 1,j]。显然必须要满足2个子区间能够完全覆盖[i , j],即要求\((2^(k-1)^ + i -1) + 1 >= (j - 2^(k-1)^ +1)\),易得2^k >= (j - i + 1),取k的最小值,使2个子区间尽可能的短。所以k = log2(j - i + 1);(向下取整)

所以对于每次询问易得\(ans = max(dp[i][k],dp[j - 2^k + 1][k])\)

3.小技巧
cmath中log2函数效率较低,所以可以用递推处理log2d的值,我们用lo代表log2d向下取整的值,则lo[d] = lo[d/2]+1。

#include<bits/stdc++.h>
using namespace std;
const int N=2*1e5+5; 
int n,m,l,r,f[N][20],lo[N];

int main()
{
	scanf("%d",&n);
	
	lo[0]=-1;
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&f[i][0]);
		lo[i]=lo[i/2]+1;
	}
	
	for(int j=1;j<=lo[n];++j)
	for(int i=1;i+(1<<j)-1<=n;++i)
	f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
	scanf("%d",&m);
	while(m--)
	{
		scanf("%d %d",&l,&r);
		int k=lo[r-l+1];
		printf("%d\n",max(f[l][k],f[r-(1<<k)+1][k]));
	}
}

【USACO 2007 Jan. Gold】Balanced Lineup

【题目描述】FJ 的 N 头牛总是按同一序列排队。有一天,FJ 决定让一些牛玩一场飞盘比赛。他准备找一群在对列中为置连续的牛来进行比赛,但是为了避免水平悬殊,牛的身高不应该相差太大。FJ 准备了 Q 个可能的牛的选择和所有牛的身高。他想知道每一组里面最高和最低的牛的身高差别。

【数据输入】第一行:N 和 Q;

第二至第 N+1 行,第 i+1 行是第 i 头牛的身高 hi ;

第 N+2 至第 N+Q+1 行,每行两个整数 A 和 B,表示从 A 到 B 的所有牛。

【数据输出】第一至第 Q 行,每行一个整数,表示对于询问的回答(即最高和最低的牛的身高差)。

【输入样例】6 3
1
7
3
4
2
5
1 5
4 6
2 2

【输出样例】6
3
0

【数据范围】对于全部数据,1≤N≤5×104,1≤Q≤1.8×105,1≤hi≤106,1≤A≤B≤N。

#include<bits/stdc++.h>
using namespace std;
const int N=50005; 
int n,m,l,r,f[N][20],ff[N][20],lo[N];

int main()
{
	scanf("%d %d",&n,&m);
	
	lo[0]=-1;
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&f[i][0]);
		ff[i][0]=f[i][0];
		lo[i]=lo[i/2]+1;
	}
	
	for(int j=1;j<=lo[n];++j)
	for(int i=1;i+(1<<j)-1<=n;++i)
	{
		f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
		ff[i][j]=min(ff[i][j-1],ff[i+(1<<j-1)][j-1]);
	}
	while(m--)
	{
		scanf("%d %d",&l,&r);
		int k=lo[r-l+1];
		printf("%d\n",max(f[l][k],f[r-(1<<k)+1][k])-min(ff[l][k],ff[r-(1<<k)+1][k]));
	}
}
posted @ 2020-10-23 20:39  林生。  阅读(120)  评论(0)    收藏  举报