题意: T ( 1 ≤ T ≤ 100 ) T(1\leq T \leq 100) T(1≤T≤100)组测试点,每次给定 n ( 1 ≤ n ≤ 5 × 1 0 4 ) n(1\leq n\leq 5\times 10^4) n(1≤n≤5×104)个整数对 ( a i , b i ) , ( 1 ≤ a i , b i ≤ 1 0 18 ) (a_i,b_i), (1\leq a_i,b_i\leq 10^{18}) (ai,bi),(1≤ai,bi≤1018),接下来进行 q ( 1 ≤ q ≤ 5 × 1 0 4 ) q(1\leq q\leq 5\times 10^4) q(1≤q≤5×104)次询问,每次询问给出一个整数对 ( A , B ) , ( 1 ≤ A , B ≤ 1 0 18 ) (A,B),(1\leq A,B\leq 10^{18}) (A,B),(1≤A,B≤1018),你有一种操作可以令 ( A , B ) (A,B) (A,B)变为 ( A + B , B ) (A+B,B) (A+B,B)或 ( A , B + A ) (A,B+A) (A,B+A),你可以对 ( A , B ) (A,B) (A,B)进行任意次数操作,求这 n n n对 ( a i , b i ) (a_i,b_i) (ai,bi)中有几对可以被得到。 ∑ n , ∑ q ≤ 5 × 1 0 5 \sum n,\sum q \leq 5\times 10^5 ∑n,∑q≤5×105
题解:
观察
(
A
,
B
)
→
(
A
+
B
,
B
)
,
(
A
,
B
+
A
)
(A,B)\to(A+B,B),(A,B+A)
(A,B)→(A+B,B),(A,B+A)这个操作,不难联想到辗转相减法,又能联想到辗转相除法/欧几里得算法。
我们先考虑对于某一组特定的
(
a
,
b
)
(a,b)
(a,b),什么样的询问
(
A
,
B
)
(A,B)
(A,B)可以经过多次操作得到
(
a
,
b
)
(a,b)
(a,b)
不妨设
a
>
b
a>b
a>b,将其逆向操作,发现
(
a
,
b
)
,
(
a
−
b
,
b
)
,
(
a
−
2
b
,
b
)
,
.
.
.
,
(
a
m
o
d
b
,
b
)
(a,b),(a-b,b),(a-2b,b),...,(a\bmod b,b)
(a,b),(a−b,b),(a−2b,b),...,(amodb,b)都可以经过操作得到
(
a
,
b
)
(a,b)
(a,b),同样地对
(
a
′
=
a
m
o
d
b
,
b
)
(a'=a\bmod b,b)
(a′=amodb,b)进行进一步缩小得到的
(
a
′
,
b
)
,
(
a
′
,
b
−
a
′
)
,
(
a
′
,
b
−
2
a
′
)
,
.
.
,
(
a
′
,
b
m
o
d
a
′
)
(a',b),(a',b-a'),(a',b-2a'),..,(a',b\bmod a')
(a′,b),(a′,b−a′),(a′,b−2a′),..,(a′,bmoda′),也可以经过操作得到
(
a
,
b
)
(a,b)
(a,b)。
但是当
a
b
\frac{a}{b}
ba过大时中间的pair太多,因此需要进行压缩。对它进行欧几里得算法的一轮迭代会变为
(
a
m
o
d
b
,
b
)
(a\bmod b, b)
(amodb,b),我们可以储存
(
a
m
o
d
b
,
b
)
→
a
(a\bmod b,b) \to a
(amodb,b)→a这个映射,当询问的
(
A
,
B
)
(A,B)
(A,B)满足
A
>
B
A>B
A>B、
B
=
b
B=b
B=b、
A
m
o
d
b
=
a
m
o
d
b
A\bmod b= a\bmod b
Amodb=amodb、
A
≤
a
A\leq a
A≤a时,可以将
(
A
,
B
)
(A,B)
(A,B)变为
(
A
+
k
b
,
B
)
(A+kb,B)
(A+kb,B)得到
(
a
,
b
)
(a,b)
(a,b)。
举个例子,
(
a
,
b
)
=
(
18
,
5
)
(a,b)=(18,5)
(a,b)=(18,5),我们储存了
(
3
,
5
)
→
18
(3,5)\to18
(3,5)→18这个映射。现在询问的
(
A
,
B
)
=
(
8
,
5
)
(A,B)=(8,5)
(A,B)=(8,5),检查
(
A
m
o
d
B
,
B
)
=
(
3
,
5
)
(A\bmod B,B)=(3,5)
(AmodB,B)=(3,5),发现
B
=
b
=
5
B=b=5
B=b=5且
A
=
8
≤
a
=
18
A=8\leq a=18
A=8≤a=18,所以
(
8
,
5
)
(8,5)
(8,5)可以变为
(
8
+
2
×
5
,
5
)
=
(
18
,
5
)
(8+2\times 5,5)=(18,5)
(8+2×5,5)=(18,5)
另外需要注意当 a i = b i a_i=b_i ai=bi时,不可能从 A ≠ B A\neq B A=B的 ( A , B ) (A,B) (A,B)得到,只能从 A = B = a i = b i A=B=a_i=b_i A=B=ai=bi的 ( A , B ) (A,B) (A,B)得到。
上述的方法是我的主要思路,接下来还要全面地考虑,我们需要储存这三样东西
1、记录映射1: a < b , ( a , b m o d a ) → S e t ( b ) a<b,(a,b\bmod a)\to Set(b) a<b,(a,bmoda)→Set(b),其中 ( a , b ) (a,b) (a,b)是有所有 ( a i , b i ) (a_i,b_i) (ai,bi)经过欧几里得算法得到的满足 a < b a<b a<b的pair, S e t ( b ) Set(b) Set(b)表示得到 ( a , b m o d a ) (a,b\bmod a) (a,bmoda)的上一步的 b b b
2、记录映射2: a > b , ( a m o d b , b ) → S e t ( a ) a>b,(a\bmod b,b)\to Set(a) a>b,(amodb,b)→Set(a),其中 ( a , b ) (a,b) (a,b)是有所有 ( a i , b i ) (a_i,b_i) (ai,bi)经过欧几里得算法得到的满足 a > b a>b a>b的pair, S e t ( a ) Set(a) Set(a)表示得到 ( a m o d b , b ) (a\bmod b,b) (amodb,b)的上一步的 a a a
3、记录 a i = b i = x a_i=b_i=x ai=bi=x的数量
例如 ( 22 , 8 ) (22,8) (22,8)经过欧几里得算法迭代得到 ( 6 , 8 ) , ( 6 , 2 ) , ( 0 , 2 ) (6,8),(6,2),(0,2) (6,8),(6,2),(0,2),则映射1种储存了 ( 6 , 2 ) → { 8 } (6,2)\to\{8\} (6,2)→{8},映射2中储存了 ( 6 , 8 ) → { 22 } (6,8)\to\{22\} (6,8)→{22}, ( 0 , 2 ) → { 6 } (0,2)\to \{6\} (0,2)→{6}
对于询问的 ( A , B ) (A,B) (A,B)
1、 A > B A>B A>B,需要在映射2中查找 k e y = ( A m o d B , B ) key=(A\bmod B, B) key=(AmodB,B)对应的 S e t ( a ) Set(a) Set(a), S e t ( a ) Set(a) Set(a)中大于等于 A A A的元素个数即为答案
2、 A < B A<B A<B,需要在映射1中查找 k e y = ( A , B m o d A ) key=(A, B\bmod A) key=(A,BmodA)对应的 S e t ( b ) Set(b) Set(b), S e t ( b ) Set(b) Set(b)中大于等于 B B B的元素个数即为答案
3、 A = B A=B A=B,需要在映射1中查找 k e y = ( A , 0 ) key=(A, 0) key=(A,0)对应的 S e t ( b ) Set(b) Set(b),答案加上 S e t ( b ) Set(b) Set(b)中大于等于 B B B的元素个数;在映射2中查找 k e y = ( 0 , B ) key=(0, B) key=(0,B)对应的 S e t ( a ) Set(a) Set(a),答案加上 S e t ( a ) Set(a) Set(a)中大于等于 A A A的元素个数;答案加上 a i = b i = A = B a_i=b_i=A=B ai=bi=A=B的个数。
我定义的 S e t Set Set可以用vector实现,预处理时对所有 a i , b i a_i,b_i ai,bi使用欧几里得算法进行迭代,存入map<pair<ll,ll>,vector<ll>>,做完 n n n个pair后对map中所有vector进行排序。询问时二分查找出大于等于某个数的个数。
接下俩来析复杂度:
令值域 C = 1 0 18 C=10^{18} C=1018,每一对 ( a i , b i ) (a_i,b_i) (ai,bi)会加入 O ( log C ) O(\log C) O(logC)个映射,共 O ( n log C ) O(n\log C) O(nlogC)个pair。map上插入、查询复杂度 O ( log ( n log C ) ) O(\log (n\log C)) O(log(nlogC)) or O ( 1 ) O(1) O(1),将所有pair存入map总复杂度为 O ( n log C log ( n log C ) ) O(n\log C \log (n\log C)) O(nlogClog(nlogC)) or O ( n log n log C ) O(n\log n \log C) O(nlognlogC)。(or后面的是用hashmap代替map,下文中同理)
因为对map的某个key,每一对 ( a i , b i ) (a_i,b_i) (ai,bi)最多为该key对应的vector提供一个元素,因此每个vector中最多 n n n个元素。所有vector中元素个数之和为 O ( n log C ) O(n\log C) O(nlogC),全部排序一遍的复杂度为 O ( ∑ s i z e i log s i z e i ) O(\sum size_i\log size_i) O(∑sizeilogsizei),不大于 O ( n log n log C ) O(n\log n \log C) O(nlognlogC),最坏情况为有 log C \log C logC个长为 n n n的vector。
每次询问需要在map上根据key进行查询 O ( log ( n log C ) ) O(\log (n\log C)) O(log(nlogC)) or O ( 1 ) O(1) O(1),并在vector上二分查找 O ( log n ) O(\log n) O(logn),查询总复杂度为 O ( q log ( n log C ) ) O(q\log (n\log C)) O(qlog(nlogC))。另外还需要储存 a = b a=b a=b的个数,时间复杂度为 O ( n log n + q log n ) O(n\log n + q\log n) O(nlogn+qlogn),就忽略了。
综上,时间复杂度为 O ( n log C log ( n log C ) + q log ( n log C ) ) O(n\log C \log (n\log C)+q\log(n\log C)) O(nlogClog(nlogC)+qlog(nlogC)),空间复杂度为 O ( n log C ) O(n\log C) O(nlogC)。顺带提一句, log ( n log C ) = log n + log log C \log (n\log C)=\log n+\log \log C log(nlogC)=logn+loglogC,而 log log C < log n \log \log C < \log n loglogC<logn,因此 O ( log ( n log C ) ) O(\log (n\log C)) O(log(nlogC))同 O ( log n ) O(\log n) O(logn)。我的程序运行时间是7.5秒左右,时限只有10秒,还好常数不太大。当然也可以用unordered_map代替map,自定义一个哈希函数,例如 ( a , b ) (a,b) (a,b)的哈希值为 [ a × ( 1 0 18 + 7 ) + b ] m o d ( 1 0 9 + 9 ) [a\times (10^{18}+7) + b ]\bmod (10^9+9) [a×(1018+7)+b]mod(109+9),将复杂度优化为 O ( n log n log C + q log n ) O(n\log n \log C + q\log n) O(nlognlogC+qlogn) (理论复杂度实际上没有变,只是常数稍微减小)
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 5e4 + 5;
int n, q;
ll a[N], b[N];
map<pair<ll, ll>, vector<ll>> mpA; // a>b (a,b)->vector b+ka
map<pair<ll, ll>, vector<ll>> mpB; // a<b (a,b)->vector a+kb
map<ll, int> mpC; // mpC[x]: a=b=x的个数
void work() {
mpA.clear();
mpB.clear();
mpC.clear();
cin >> n >> q;
for (int i = 1; i <= n; ++i) {
cin >> a[i] >> b[i];
}
for (int i = 1; i <= n; ++i) {
ll x = a[i], y = b[i];
if (x == y) {
++mpC[x];
}
else {
while (x && y) {
if (x > y) {
ll tx = x;
x %= y;
mpB[{x, y}].emplace_back(tx);
}
else {
ll ty = y;
y %= x;
mpA[{x, y}].emplace_back(ty);
}
}
}
}
for (auto &pr: mpA) {
sort(pr.second.begin(), pr.second.end());
}
for (auto &pr: mpB) {
sort(pr.second.begin(), pr.second.end());
}
while (q--) {
ll A, B;
cin >> A >> B;
ll ans = 0;
if (A > B) {
auto it = mpB.find({A % B, B});
if (it != mpB.end()) {
auto &v = it->second;
ans = v.end() - lower_bound(v.begin(), v.end(), A);
}
}
else if (A == B) {
auto it1 = mpA.find({A, 0});
if (it1 != mpA.end()) {
auto &v1 = it1->second;
ans += v1.end() - lower_bound(v1.begin(), v1.end(), A);
}
auto it2 = mpB.find({0, A});
if (it2 != mpB.end()) {
auto &v2 = it2->second;
ans += v2.end() - lower_bound(v2.begin(), v2.end(), A);
}
auto it3 = mpC.find(A);
if (it3 != mpC.end()) {
ans += it3->second;
}
}
else {
auto it = mpA.find({A, B % A});
if (it != mpA.end()) {
auto &v = it->second;
ans = v.end() - lower_bound(v.begin(), v.end(), B);
}
}
cout << ans << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T = 1;
cin >> T;
while (T--) {
work();
}
}