题意
传送门 NC 15557
题解
如果只考虑区间的
g
c
d
gcd
gcd,根据其性质
g
c
d
(
a
,
b
,
c
,
…
)
=
g
c
d
(
a
,
g
c
d
(
b
,
c
,
…
)
)
gcd(a,b,c,\dots)=gcd(a,gcd(b,c,\dots))
gcd(a,b,c,…)=gcd(a,gcd(b,c,…)) 线段树维护区间
g
c
d
gcd
gcd 即可;合并满足子区间
g
c
d
gcd
gcd 等于区间
g
c
d
gcd
gcd 的子区间数需要维护更多信息。
考虑区间拓展其
g
c
d
gcd
gcd 的不减性质
g
c
d
(
a
,
b
)
≥
g
c
d
(
a
,
b
,
c
)
gcd(a,b)\geq gcd(a,b,c)
gcd(a,b)≥gcd(a,b,c) 那么可以用尺取法统计满足条件的子区间数;若
g
c
d
gcd
gcd 减小,相对于原值至少约去一个质因子,考虑数据范围
[
0
,
1
0
9
]
[0,10^9]
[0,109],那么对于区间前缀或者后缀的
g
c
d
gcd
gcd 最多有
32
32
32 (最小质因子
2
2
2 以及数据范围最小值
0
0
0,考虑
2
30
2^{30}
230)个不同的值且是区间连续的。那么可以将区间左右端点拓展(即
[
l
,
i
]
,
[
i
,
r
]
,
i
∈
[
l
,
r
]
[l,i],[i,r],i\in [l,r]
[l,i],[i,r],i∈[l,r])的各子区间压缩,维护
g
c
d
gcd
gcd 相等的连续区间及其长度。此时可以在
O
(
l
o
g
(
m
a
x
{
a
i
}
)
)
O(log(max\{a_i\}))
O(log(max{ai})) 实现区间合并,总复杂度
O
(
n
×
l
o
g
(
m
a
x
{
a
i
}
)
+
q
×
l
o
g
(
n
)
×
l
o
g
(
m
a
x
{
a
i
}
)
)
O(n\times log(max\{a_i\})+q\times log(n)\times log(max\{a_i\}))
O(n×log(max{ai})+q×log(n)×log(max{ai}))。
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 100005
#define t_size (1 << 18) - 1
typedef long long ll;
struct seg{int x, n;};
struct node {ll cnt; int gcd, nl, nr; seg l[25], r[25];} tree[t_size];
int T, N, Q, A[maxn], L[maxn], R[maxn];
node merge(node &cl, node &cr)
{
node p;
p.gcd = __gcd(cl.gcd, cr.gcd);
p.cnt = (cl.gcd == p.gcd ? cl.cnt : 0) + (cr.gcd == p.gcd ? cr.cnt : 0);
for (int i = 0, j = cr.nl - 1, rs = 0; i < cl.nr; i++)
{
while (j >= 0 && __gcd(cl.r[i].x, cr.l[j].x) == p.gcd)
{
rs += cr.l[j].n;
--j;
}
p.cnt += 1LL * cl.r[i].n * rs;
}
p.nl = cl.nl, p.nr = cr.nr;
for (int i = 0; i < cl.nl; i++) p.l[i] = cl.l[i];
for (int i = 0; i < cr.nr; i++) p.r[i] = cr.r[i];
for (int i = 0; i < cr.nl; i++)
{
int t = __gcd(p.l[p.nl - 1].x, cr.l[i].x);
if (p.l[p.nl - 1].x == t) p.l[p.nl - 1].n += cr.l[i].n;
else p.l[p.nl++] = seg{t, cr.l[i].n};
}
for (int i = 0; i < cl.nr; i++)
{
int t = __gcd(p.r[p.nr - 1].x, cl.r[i].x);
if (p.r[p.nr - 1].x == t) p.r[p.nr - 1].n += cl.r[i].n;
else p.r[p.nr++] = seg{t, cl.r[i].n};
}
return p;
}
void init(int k, int l, int r)
{
if (r - l == 1)
{
node &p = tree[k];
p.gcd = A[l];
p.cnt = p.nl = p.nr = 1;
p.l[0] = p.r[0] = seg{A[l], 1};
}
else
{
int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (r + l) >> 1;
init(chl, l, m);
init(chr, m, r);
tree[k] = merge(tree[chl], tree[chr]);
}
}
node query(int a, int b, int k, int l, int r)
{
if (a <= l && r <= b) return tree[k];
int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (l + r) >> 1;
if (b <= m) return query(a, b, chl, l, m);
if (m <= a) return query(a, b, chr, m, r);
node p1 = query(a, b, chl, l, m), p2 = query(a, b, chr, m, r);
return merge(p1, p2);
}
int main()
{
scanf("%d", &T);
for (int i = 1; i <= T; i++)
{
scanf("%d", &N);
for (int i = 0; i < N; i++) scanf("%d", A + i);
init(0, 0, N);
scanf("%d", &Q);
for (int i = 0; i < Q; i++) scanf("%d%d", L + i, R + i);
printf("Case #%d:\n", i);
for (int i = 0; i < Q; i++)
{
node p = query(L[i] - 1, R[i], 0, 0, N);
printf("%d %lld\n", p.gcd, p.cnt);
}
}
return 0;
}
测试没有卡 2 30 2^{30} 230 这样的数据,数组开小些也能过,开到 32 32 32 直接 T L E TLE TLE;显然用 v e c t o r vector vector 维护左右端点拓展区间的信息才是正解。
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
#define maxn 100005
#define t_size (1 << 18) - 1
typedef long long ll;
struct seg{int x, n;};
struct node {ll cnt; int gcd; vector<seg> l, r;} tree[t_size];
int T, N, Q, A[maxn], L[maxn], R[maxn];
node merge(node &cl, node &cr)
{
node p;
p.gcd = __gcd(cl.gcd, cr.gcd);
p.cnt = (cl.gcd == p.gcd ? cl.cnt : 0) + (cr.gcd == p.gcd ? cr.cnt : 0);
for (int i = 0, j = cr.l.size() - 1, rs = 0; i < cl.r.size(); i++)
{
while (j >= 0 && __gcd(cl.r[i].x, cr.l[j].x) == p.gcd)
{
rs += cr.l[j].n;
--j;
}
p.cnt += 1LL * cl.r[i].n * rs;
}
p.l = cl.l, p.r = cr.r;
for (int i = 0; i < cr.l.size(); i++)
{
int t = __gcd(p.l[p.l.size() - 1].x, cr.l[i].x);
if (p.l[p.l.size() - 1].x == t) p.l[p.l.size() - 1].n += cr.l[i].n;
else p.l.push_back(seg{t, cr.l[i].n});
}
for (int i = 0; i < cl.r.size(); i++)
{
int t = __gcd(p.r[p.r.size() - 1].x, cl.r[i].x);
if (p.r[p.r.size() - 1].x == t) p.r[p.r.size() - 1].n += cl.r[i].n;
else p.r.push_back(seg{t, cl.r[i].n});
}
return p;
}
void init(int k, int l, int r)
{
if (r - l == 1)
{
node &p = tree[k];
p.gcd = A[l], p.cnt = 1;
p.l.clear();
p.r.clear();
p.l.push_back(seg{A[l], 1});
p.r.push_back(seg{A[l], 1});
}
else
{
int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (r + l) >> 1;
init(chl, l, m);
init(chr, m, r);
tree[k] = merge(tree[chl], tree[chr]);
}
}
node query(int a, int b, int k, int l, int r)
{
if (a <= l && r <= b) return tree[k];
int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (l + r) >> 1;
if (b <= m) return query(a, b, chl, l, m);
if (m <= a) return query(a, b, chr, m, r);
node p1 = query(a, b, chl, l, m), p2 = query(a, b, chr, m, r);
return merge(p1, p2);
}
int main()
{
scanf("%d", &T);
for (int i = 1; i <= T; i++)
{
scanf("%d", &N);
for (int i = 0; i < N; i++) scanf("%d", A + i);
init(0, 0, N);
scanf("%d", &Q);
for (int i = 0; i < Q; i++) scanf("%d%d", L + i, R + i);
printf("Case #%d:\n", i);
for (int i = 0; i < Q; i++)
{
node p = query(L[i] - 1, R[i], 0, 0, N);
printf("%d %lld\n", p.gcd, p.cnt);
}
}
return 0;
}