题目大意:
题目链接:https://jzoj.net/senior/#main/show/3895
给出序列
a
a
a,求最长的子序列
a
l
∼
a
r
a_l\sim a_r
al∼ar,使得
l
∼
r
l\sim r
l∼r中有一个位置
k
k
k能被所有
l
∼
r
l\sim r
l∼r中的数整除。
思路:
暴力 O ( n 2 ) O(n^2) O(n2)似乎可以过诶~
以上不重要 ↑ \uparrow ↑
明显可以用
S
T
ST
ST表来做。维护两个数组
M
i
n
[
i
]
[
j
]
Min[i][j]
Min[i][j]和
G
c
d
[
i
]
[
j
]
Gcd[i][j]
Gcd[i][j]。其中
M
i
n
[
i
]
[
j
]
Min[i][j]
Min[i][j]表示以
i
i
i开始,接下来
2
j
2^j
2j位的最小值,
G
c
d
[
i
]
[
j
]
Gcd[i][j]
Gcd[i][j]表示以
i
i
i开始,接下来
2
j
2^j
2j位的最大公约数。
很明显
M
i
n
[
i
]
[
j
]
=
m
i
n
(
M
i
n
[
i
]
[
j
−
1
]
,
M
i
n
[
i
+
(
1
<
<
(
j
−
1
)
)
]
[
j
−
1
]
)
Min[i][j]=min(Min[i][j-1],Min[i+(1<<(j-1))][j-1])
Min[i][j]=min(Min[i][j−1],Min[i+(1<<(j−1))][j−1])
G
c
d
[
i
]
[
j
]
=
_
_
g
c
d
(
G
c
d
[
i
]
[
j
−
1
]
,
G
c
d
[
i
+
(
1
<
<
(
j
−
1
)
)
]
[
j
−
1
]
)
Gcd[i][j]=\_\_gcd(Gcd[i][j-1],Gcd[i+(1<<(j-1))][j-1])
Gcd[i][j]=__gcd(Gcd[i][j−1],Gcd[i+(1<<(j−1))][j−1])
然后回答就可以了。
时间复杂度
O
(
n
l
o
g
n
)
O(n\ logn)
O(n logn)。
如果您是
j
u
l
a
o
julao
julao可以使用不带修改的线段树和优秀的玄学
O
(
n
)
O(n)
O(n)暴力。
代码:
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
const int N=500010;
const int LG=20;
int n,l,r,mid,Min[N][LG+1],Gcd[N][LG+1];
queue<int> q;
bool check(int len) //二分长度
{
int k=log2(len);
bool ok=0;
for (int i=1;i<=n-len+1;i++)
{
int minn=min(Min[i][k],Min[i+len-(1<<k)][k]);
int GCD=__gcd(Gcd[i][k],Gcd[i+len-(1<<k)][k]);
if (minn==GCD)
{
if (!ok)
{
ok=1;
while (q.size()) q.pop();
}
q.push(i); //答案队列
}
}
return ok;
}
int main()
{
scanf("%d",&n);
memset(Min,0x3f3f3f3f,sizeof(Min));
for (int i=1;i<=n;i++)
{
scanf("%d",&Min[i][0]);
Gcd[i][0]=Min[i][0];
}
for (int j=1;j<=LG;j++)
for (int i=1;i+(1<<(j-1))<=n;i++) //RMQ不解释
{
Min[i][j]=min(Min[i][j-1],Min[i+(1<<(j-1))][j-1]);
Gcd[i][j]=__gcd(Gcd[i][j-1],Gcd[i+(1<<(j-1))][j-1]);
}
l=1;
r=n;
while (l<=r) //二分答案长度
{
mid=(l+r)/2;
if (check(mid)) l=mid+1;
else r=mid-1;
}
printf("%d %d\n",q.size(),l-2);
while (q.size())
{
printf("%d ",q.front());
q.pop();
}
return 0;
}