2.数字对
【题目描述】
小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.
【样例输入1】
本以为是个线段树取gcd和min的数据结构乱搞题。但是这样似乎肯定要两个log。目测会tle. 然后想到好久没写的st表似乎还是可以用一用的。。
枚举最小点。向左右分别用二分扩展。st表维护gcd。。。然后就只剩一个log了耶
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 500010
using namespace std;
int n,a[maxn],f[maxn][20],p[maxn],mxx,mxl,sum,Ans[maxn];
struct node{
int len,L;
}ans[maxn];
int init(){
int x=0;char s=getchar();
while(s<'0'||s>'9')s=getchar();
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
return x;
}
int cmp(const node &x,const node &y){
if(x.len==y.len)return x.L<y.L;
return x.len>y.len;
}
int Gcd(int a,int b){
return !b?a:Gcd(b,a%b);
}
int Get_pow(int x){
for(int i=0;;i++)
if((1<<i)>x)return i-1;
}
void Get_ST(){
for(int i=1;i<=n;i++)
f[i][0]=a[i];
for(int j=1;j<=18;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=Gcd(f[i][j-1],f[i+(1<<(j-1))][j-1]);
for(int i=1;i<=n;i++)
p[i]=Get_pow(i);
}
int find(int l,int r){
if(l>r)return 0;
int len=p[r-l+1];
return Gcd(f[l][len],f[r-(1<<len)+1][len]);
}
int main()
{ n=init();
for(int i=1;i<=n;i++)
a[i]=init();
Get_ST();
for(int k=1;k<=n;k++){
int li=k,ri=k,gcd=a[k],l,r;
l=0;r=n-k+1;
while(l<=r){
int mid=l+r>>1;
int x=find(k,k+mid-1);
if(x==gcd){
ri=k+mid-1;l=mid+1;
}
else r=mid-1;
}
l=0;r=k;
while(l<=r){
int mid=l+r>>1;
int x=find(k-mid+1,k);
if(x==gcd){
li=k-mid+1;l=mid+1;
}
else r=mid-1;
}
ans[k].len=ri-li;ans[k].L=li;
}
sort(ans+1,ans+1+n,cmp);
mxx=ans[1].len;mxl=ans[1].L;
Ans[++sum]=mxl;
for(int k=2;k<=n;k++)
if(ans[k].len!=mxx)break;
else if(ans[k].L!=mxl){
Ans[++sum]=ans[k].L;
mxl=ans[k].L;
}
printf("%d %d\n",sum,mxx);
for(int i=1;i<=sum;i++)
printf("%d ",Ans[i]);
return 0;
}