数字对

Posted on 2019-02-03 09:47  Mocking_Jimmy  阅读(176)  评论(0编辑  收藏  举报

1月25日

Description

小H是个善于思考的学生,现在她又在思考一个有关序列的问题。
她的面前浮现出一个长度为n的序列 \(a_i\) ,她想找出一段区间[L, R] \((1\leq L\leq R\leq n)\) 。这个特殊区间满足,存在一个k \((L\leq k\leq R)\) ,并且对于任意的i \((L\leq i\leq R)\)\(a_i\) 都能被 \(a_k\) 整除。这样的一个特殊区间[L, R]价值为 \(R - L\) 。小H想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些区间又分别是哪些呢?你能帮助她吧。

Input

第一行,一个整数n.

第二行,n个整数,代表 \(a_i\) .

Output

第一行两个整数,numval,表示价值最大的特殊区间的个数以及最大价值。

第二行num个整数,按升序输出每个价值最大的特殊区间的L.

Sample Input

输入1:

5
4 6 9 3 6

输入2:

5
2 3 5 7 11

Sample Output

输出1:

1 3
2

输出2:

5 0
1 2 3 4 5

Data Constraint

30%: \(1 <= n <= 30\) , \(1 <= ai <= 32\).

60%: \(1 <= n <= 3000\) , \(1 <= ai <= 1024\).

80%: \(1 <= n <= 300000\) , \(1 <= ai <= 1048576\).

100%: \(1 <= n <= 500000\) , \(1 <= ai < 2 ^ {31}\).

Solution

比赛的时候我看了一眼题目,然后闭眼打了一个退化的暴力二分,直接不管了。然后60分滚蛋。可以欣赏一下。

#include<cstdio>
#include<algorithm>
using namespace std;

int n,a[500002],num,val;
long long sum[500002];
int l,r,mid,minn;
int ans[500002];

void fmin(int x,int y)
{
	minn=2e9;
	for(int i=x;i<=y;i++)
		minn=min(minn,a[i]);
}
bool permod(int x,int y,int z)
{
	for(int i=x;i<=y;i++)
		if(a[i]%z!=0) return false;
	return true;
}
bool check(int x)
{
	for(int i=1;i<=n-x+1;i++)
	{
		fmin(i,i+x-1);
		if((sum[i+x-1]-sum[i-1])%minn!=0) continue;
		if(!permod(i,i+x-1,minn)) continue;
		return true;
	}
	return false;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	l=0;r=n+1;
	while(l+1<r)
	{
		mid=(l+r)/2;
		if(check(mid)) l=mid;
		else r=mid;
	}
	for(int i=1;i<=n-l+1;i++)
	{
		fmin(i,i+l-1);
		if((sum[i+l-1]-sum[i-1])%minn!=0) continue;
		if(!permod(i,i+l-1,minn)) continue;
		num++;ans[num]=i;
	}
	val=l-1;
	printf("%d %d\n",num,val);
	for(int i=1;i<=num;i++)
		printf("%d ",ans[i]);
	return 0;
}

咳咳,好了说正解。。。有两种。

  1. \(O(nlogn)\)
  2. \(O(n)\)非常玄学,不会打

首先用RMQ算法,维护最小值。这个算法非常优秀,预处理 \(O(nlogn)\) ,查询却是 \(O(1)\) 。额我实在是懒得引用了,自己查来复习。刘汝佳的书上有。

https://www.cnblogs.com/YSFAC/p/7189571

\(by\) Fugtemypt


RMQ习题:https://jzoj.net/junior/#contest/home/1532

然后就是最懵逼的地方--用RMQ求区间gcd。因为如果一个区间符合题意,那它的最小值就是这个区间的gcd(PS:STL自带一个gcd,函数名称叫__gcd)。

https://blog.csdn.net/cj1064789374/article/details/85217974

\(by\) _ Jim _

这个代码

Memory Code Length
122.27 MB 1352 bytes

放心食用(第一次写RMQ,贼丑)

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;

int n,a[500002],num,val;
long long sum[500002];
int l,r,mid,minn,gcdn;
int ans[500002];
int f[500002][30];
int st[500002][30];

void rmq_gcd_init()
{
	int lim=log2(n);
	for(int i=1;i<=n;i++)
		st[i][0]=a[i];
	for(int j=1;j<=lim;j++)
		for(int i=1;i<=n;i++)
			if(i+(1<<j)-1<=n)
				st[i][j]=__gcd(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
void gcd(int x,int y)
{
	int k=log2(y-x+1);
	gcdn=__gcd(st[x][k],st[y-(1<<k)+1][k]);
}
void rmq_init()
{
	int lim=log2(n);
	for(int i=1;i<=n;i++)
		f[i][0]=a[i];
	for(int j=1;j<=lim;j++)
		for(int i=1;i<=n;i++)
			if(i+(1<<j)-1<=n)
				f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
		
}
void rmq(int x,int y)
{
	int k=0;
	while((1<<(k+1))<=y-x+1) k++;
	minn=min(f[x][k],f[y-(1<<k)+1][k]);
}
bool check(int x)
{
	for(int i=1;i<=n-x+1;i++)
	{
		rmq(i,i+x-1);
		gcd(i,i+x-1);
		if(minn==gcdn)
			return true;
	}
	return false;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	rmq_init();
	rmq_gcd_init();
	l=0;r=n+1;
	while(l+1<r)
	{
		mid=(l+r)/2;
		if(check(mid)) l=mid;
		else r=mid;
	}
	for(int i=1;i<=n-l+1;i++)
	{
		rmq(i,i+l-1);
		gcd(i,i+l-1);
		if(minn==gcdn)
			ans[++num]=i;
	}
	val=l-1;
	printf("%d %d\n",num,val);
	for(int i=1;i<=num;i++)
		printf("%d ",ans[i]);
	return 0;
}

关于\(O(n)\)

就算我记下来思路也写不出来

首先我们枚举k(题中的k),然后以 \(O(n)\) 预处理出k之前的能被k整除的最长区间的长L[k],然后同理预处理出R[k]。接下来,你懂得。