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
第一行两个整数,num
和val
,表示价值最大的特殊区间的个数以及最大价值。
第二行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;
}
咳咳,好了说正解。。。有两种。
- \(O(nlogn)\)
- \(O(n)\)
非常玄学,不会打
首先用RMQ算法,维护最小值。这个算法非常优秀,预处理 \(O(nlogn)\) ,查询却是 \(O(1)\) 。额我实在是懒得引用了,自己查来复习。刘汝佳的书上有。
https://www.cnblogs.com/YSFAC/p/7189571
\(by\) Fugtemypt
然后就是最懵逼的地方--用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]
。接下来,你懂得。