数字对
Posted mocking-jimmy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数字对相关的知识,希望对你有一定的参考价值。
1月25日
Description
小H是个善于思考的学生,现在她又在思考一个有关序列的问题。
她的面前浮现出一个长度为n
的序列 (a_i) ,她想找出一段区间[L, R]
((1leq Lleq Rleq n)) 。这个特殊区间满足,存在一个k
((Lleq kleq R)) ,并且对于任意的i
((Lleq ileq 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
",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
",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]
。接下来,你懂得。
以上是关于数字对的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript - 代码片段,Snippets,Gist
LeetCode810. 黑板异或游戏/455. 分发饼干/剑指Offer 53 - I. 在排序数组中查找数字 I/53 - II. 0~n-1中缺失的数字/54. 二叉搜索树的第k大节点(代码片段