HDOJ 7311 Noblesse Code —— 2023“钉耙编程”中国大学生算法设计超级联赛(3)(2023杭电多校第三场)

题目链接

题意: T ( 1 ≤ T ≤ 100 ) T(1\leq T \leq 100) T(1T100)组测试点,每次给定 n ( 1 ≤ n ≤ 5 × 1 0 4 ) n(1\leq n\leq 5\times 10^4) n(1n5×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),(1ai,bi1018),接下来进行 q ( 1 ≤ q ≤ 5 × 1 0 4 ) q(1\leq q\leq 5\times 10^4) q(1q5×104)次询问,每次询问给出一个整数对 ( A , B ) , ( 1 ≤ A , B ≤ 1 0 18 ) (A,B),(1\leq A,B\leq 10^{18}) (A,B),(1A,B1018),你有一种操作可以令 ( 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,q5×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),(ab,b),(a2b,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,ba),(a,b2a),..,(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 Aa时,可以将 ( 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=8a=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();
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值