时间限制: 1 Sec 内存限制: 256 MB
题目描述
小H是个善于思考的学生,现在她又在思考一个有关序列的问题。 她的面前浮现出一个长度为n的序列{ai},她想找出一段区间[L, R](1 <= L <= R <= n)。 这个特殊区间满足,存在一个k(L <= k <= R),并且对于任意的i(L <= i <= R),ai都能被ak整除。这样的一个特殊区间 [L, R]价值为R – L。 小H想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些区间又分别是哪些呢?你能帮助她吧。
输入
第一行,一个整数n. 第二行,n个整数,代表ai.
输出
第一行两个整数,num和val,表示价值最大的特殊区间的个数以及最大价值。 第二行num个整数,按升序输出每个价值最大的特殊区间的L.
样例输入
5
4 6 9 3 6
样例输出
1 3
2
提示
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 <
输出末尾有空格
题解:
这题很明显是RMQ,如果不会可以参考 此文。
30分:枚举l,r,k,线性扫一遍O(n^4)
进一步思考
特殊区间的特点实际上就是区间最小值等于这个区间的GCD
不用枚举k变为
再想想?
答案一定可以二分
因此我们二分答案len,判断长度为len的区间是否有可行的
时间复杂度
用 的时间复杂度求区间最小值和GCD显然是很浪费的
一个性质:一个集合多加一个数,那么新集合的GCD就是原来集合的GCD和新数的GCD
嗯?线段树!
那就是 显然第四类数据是来卡你的
因此我们可以想到RMQ
令数组 表示从第i个数起连续2^j个数中的GCD
令数组 表示从第i个数起连续2^j个数中的最小值
预处理
计算答案时 查询
所以计算答案时时间复杂度也是
据说当时线段树连80分都不一定能拿到
#include<bits/stdc++.h>
#define N 500005
using namespace std;
int a[N],n,p,minn[N][20],gcdn[N][20],b[N];
int gcd(int a,int b)
{
if(a>b)swap(a,b);
if(a==0)return b;
return gcd(b%a,a);
}
bool work(int x,int y)
{
int k=log2(double(y-x+1));
int nmin=min(minn[x][k],minn[y-(1<<k)+1][k]);
int ngcd=gcd(gcdn[x][k],gcdn[y-(1<<k)+1][k]);
if(nmin==ngcd)return true;else
return false;
}
bool check(int len)
{
bool flag=false;
for(int i=1;i<=n-len+1;i++)
if(work(i,i+len-1))
{
p=i;
flag=true;
}
return flag;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
gcdn[i][0]=minn[i][0]=a[i];
}
for(int j=1;j<20;j++)
for(int i=1;i<=n+1-(1<<j);i++)
{
minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
gcdn[i][j]=gcd(gcdn[i][j-1],gcdn[i+(1<<(j-1))][j-1]);
}
int l=1,r=n;
while(l<r)
{
int mid=(l+r+1)>>1;
if(check(mid))l=mid;else r=mid-1;
}
int tot=0;
for(int i=1;i<=n-l+1;i++)
if(work(i,i+l-1))
b[++tot]=i;
printf("%d %d\n",tot,l-1);
for(int i=1;i<=tot;i++)
printf("%d ",b[i]);
return 0;
}