等差区间 线段树+GCD

Description

已知一个长度为 nn 的数组 a[1],a[2],,a[n]a[1],a[2],…,a[n],我们进行 qq 次询问,每次询问区间 a[l],a[l+1],,a[r1],a[r]a[l],a[l+1],…,a[r−1],a[r],数字从小到大排列后,是否会形成等差数列。等差数列的定义为,数列相邻两项(后一项减去前一项)的差值相等。

Input

本题有多组输入数据。

每组输入数据第一行输入两个正整数 nn 和 qq。第二行输入 nn 个正整数 a[1],a[2],,a[n]a[1],a[2],…,a[n]。最后输入 qq 行,每行两个数字 l,rl,r1lrn1≤l≤r≤n),表示询问区间 a[l],,a[r]a[l],…,a[r]

1n,q105,1a[i]1061≤n,q≤105,1≤a[i]≤106

Output

对于每组询问输出一行,如果形成等差数列,输出“Yes ”,否则输出“No”(不含引号)。

Sample Input

5 5
3 1 5 2 4
1 3
4 5
1 4
3 4
2 2

Sample Output

Yes
Yes
No
Yes
Yes
题目大意:给定一个数列,并给出很多区间询问,问是否是等差数列。
题解:这道题目用了一个很好的判断等差数列的思想,首先如果区间内最大值与最小值相同,那么一定是等差数列
不然的话,判断区间和与(max+min)*n/2是否相等,如不相等那么输出No,否则再判断区间gcd,如果是等差数列
那么它的区间gcd应该等于其公差(这里是我觉得最精华的地方),而公差我们可以用(max-min)/(n-1)来计算得到
其中区间最值用线段树维护,区间gcd也用线段树维护
#include <iostream>
#include <cstdio>
#include <algorithm> 
#include <cstring>
using namespace std;
#define int long long
const int MAX = 1e5+6;
const int INF = 1e9;
int n,q;
int A[MAX];
int stmax[MAX<<2];
int stgcd[MAX<<2];
int stmin[MAX<<2];
int sum[MAX];
int gcd(int a,int b)
{
	if(b == 0) return a;
	return gcd(b,a % b);
}
void init(int v,int l,int r)
{
	if(r - l == 1)
	{
		stmax[v] = stmin[v] = A[l];
		stgcd[v] = abs(A[l+1]-A[l]);
	}
	else
	{
		init(2*v+1,l,(l+r)/2);
		init(2*v+2,(l+r)/2,r);
		stmax[v] = max(stmax[2*v+1],stmax[2*v+2]);
		stmin[v] = min(stmin[2*v+1],stmin[2*v+2]);
		stgcd[v] = gcd(stgcd[2*v+1],stgcd[2*v+2]);
	}
}
int querymin(int v,int a,int b,int l,int r)
{
	if(l <= a && r >= b) return stmin[v];
	else if(b <= l || r <= a) return INF;
	else return min(querymin(2*v+1,a,(a+b)/2,l,r),querymin(2*v+2,(a+b)/2,b,l,r));
}
int querymax(int v,int a,int b,int l,int r)
{
	if(l <= a && r >= b) return stmax[v];
	else if(b <= l || r <= a) return 0;
	else return max(querymax(2*v+1,a,(a+b)/2,l,r),querymax(2*v+2,(a+b)/2,b,l,r));
}
int querygcd(int v,int a,int b,int l,int r)
{
	if(l <= a && r >= b) return stgcd[v];
	else if(b <= l || r <= a) return 0;
	else return gcd(querygcd(2*v+1,a,(a+b)/2,l,r),querygcd(2*v+2,(a+b)/2,b,l,r));
}
main()
{

	while(~scanf("%lld%lld",&n,&q))
	{
		sum[0] = 0;
		for(int i = 0;i < n;i++)
		{
			scanf(" %lld",&A[i]);
			sum[i+1] = sum[i] + A[i];
		}
		A[n] = A[n-1];
		init(0,0,n);
		for(int i = 0;i < q;++i)
		{
			int l,r;
			scanf("%lld%lld",&l,&r);
			if(r - l <= 1) 
			{
				puts("Yes");
				continue;
			}
			int mini = querymin(0,0,n,l-1,r);
			int maxi = querymax(0,0,n,l-1,r);
			if(maxi == mini) 
			{
				puts("Yes");
				continue;
			}
			//cout<<r<<" "<<l-1<<endl;
			if((sum[r] - sum[l-1])*2 != (maxi + mini) * (r-l+1))
			{
				puts("No");
				continue;
			}
			int d = (maxi - mini) / (r - l);
			if((maxi-mini)%(r-l) == 0 && querygcd(0,0,n,l-1,r-1) == d) puts("Yes");
			else puts("No");
		}
	}
	return 0;
 } 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值