• APIO 2019 奇怪装置


    题目链接

    LOJ3144
    LuoguP5444

    题目概括

    给定 \(n\) 个不相交的区间 \([l_i,r_i]\)\(A\)\(B\)

    定义一个数 \(t\) 到另一个二元组的映射为 \(t\rightarrow ((t+\lfloor \frac tB\rfloor) \; mod \; A,t \; mod \; B)\)

    问这些区间的并中所有数的映射中不同的二元组有多少个。

    数据范围:\(N\leq 10^6,A,B,l_i,r_i\leq 10^{18}\)

    思路要点

    考虑循环节大小,设循环节长度为 \(S\),二元组分别为两个函数表示

    \[x(t)=(t+\lfloor \frac tB\rfloor) \; mod \; A \]

    \[y(t)=t \; mod \; B \]

    \[x(t+S)=(t+S+\lfloor \frac {t+S}B\rfloor) \; mod \; A \]

    \[y(t+S)=(t+S) \; mod \; B \]

    \[t\equiv t+S \mod B \]

    显然

    \[S\mod B=0 \]

    那么令\(S=kB\)

    \[x(t+kB)\equiv (t+\lfloor \frac tB\rfloor+kB+k)\mod A \]

    \[k(B+1)\mod A=0 \]

    显然

    \[k=\frac A{\text{gcd}(B+1,A)} \]

    循环节大小为

    \[B\cdot \frac A{\text{gcd}(B+1,A)} \]

    然后就是离散化后线段覆盖即可

    时间复杂度 \(\Theta(nlogn)\)

    代码

    #include <bits/stdc++.h>
    #define inf 0x3f3f3f3f
    #define INF 0x3f3f3f3f3f3f3f3f
    
    using namespace std;
    
    typedef long long ll;
    typedef unsigned long long ull;
    
    const int N = 2e6 + 5;
    
    ll A, B, cir, ans; 
    int n, tot;
    pair<ll, ll> sec[N];
    
    template <class T> 
    T gcd(T x, T y) {
      return !y ? x : gcd(y, x % y); 
    }
    
    ll calc(ll x) {
      return x % cir ? x % cir : cir; 
    }
    
    int main() {
      scanf("%d %lld %lld", &n, &A, &B);
      cir = A / gcd(A, B + 1);
      if (3e18 / cir < B) 
        cir = 3e18;
      else 
        cir *= B;
      tot = n;  
      for (int i = 1; i <= n; i++) {
        scanf("%lld %lld", &sec[i].first, &sec[i].second);
        if (sec[i].second - sec[i].first + 1 >= cir) {
          printf("%lld\n", cir);
          return 0;
        }
        sec[i] = make_pair(calc(sec[i].first), calc(sec[i].second));
        if (sec[i].first > sec[i].second) {
          sec[++tot] = make_pair(sec[i].first, cir);
          sec[i] = make_pair(1, sec[i].second);
        }
      }
      sec[++tot] = make_pair(cir + 1, cir);
      sort(sec + 1, sec + 1 + tot);
      ll left = sec[1].first, right = sec[1].second;
      for (int i = 2; i <= tot; i++) {
        if (sec[i].first <= right) {
          right = max(right, sec[i].second);
        } else {
          ans += right - left + 1; 
          left = sec[i].first, right = sec[i].second;
        }
      }
      printf("%lld\n", ans);
      return 0;
    }
    

    后言

    • 对于数据较大,这题目又存在模运算,所以题目的突破口一定是找循环节。
  • 相关阅读:
    正则表达式判断手机号是否11位
    php-流程管理(发起流程和审核流程)
    php-流程管理(新建流程)(php+Ajax)
    php-人员权限管理(RBAC)
    状态压缩DP
    树形DP
    Broken Keyboard UVA 11988 数组实现链表
    Trees in a Wood UVA
    POJ1061 青蛙的约会 拓展欧几里得
    UVA10215The Largest/Smallest Box(小数精度)
  • 原文地址:https://www.cnblogs.com/chhokmah/p/12615958.html
Copyright © 2020-2023  润新知