同余分析

同余类分析法 的基本思想是:利用同余关系将全体整数(或问题的状态空间)划分为若干个互不相交的等价类。

  • 分类标准:选定一个基准数 \(M\),根据每个数 \(x\) 满足 \(x \equiv r \pmod{M}\) 的余数 \(r\) 进行归类。
  • 分析逻辑:如果一个性质对于某个余数 \(r\) 成立,那么通过给 \(x\) 不断加减 \(M\)(即在同一个同余类内平移),往往能推导出该类中所有(或大部分)元素的性质。

例题:P3951 [NOIP 2017 提高组] 小凯的疑惑

要求的是最大的表示不出来的价值,那么这个价值加一是能被表示出来的。

如果一个价值 \(n\) 能被表示出来,那么 \(n+b\) 一定可以被表示。

将价值按除以 \(b\) 的余数分成 \(b\) 组,以 \(a=3, \ b=7\) 为例:

image

除了最后一列,每一列最小的可被表示的价值实际上是 \(ka + 0b\) 的形式,由于 \(a\)\(b\) 互质,那么根据裴蜀定理,\(0a \sim (b-1)a\) 一定都覆盖得到,因此这些数中最大的那个就是 \((b-1)a\),而要求的是最大的不能被表示的,再减 \(b\) 即为答案。因此答案实际上就是 \(ab-a-b\)

参考代码
#include <cstdio>
int main()
{
    int a, b;
    scanf("%d%d", &a, &b);
    printf("%lld\n", 1ll * a * b - a - b);
    return 0;
}

习题:P10217 [省选联考 2024] 季风

给定 \(n, k,x,y\) 以及 \(n\) 天的季风向量 \((x_0,y_0), \dots, (x_{n-1},y_{n-1})\),季风向量以 \(n\) 为周期循环。每一天小 X 可以自行移动一步 \((x_i', y_i')\),满足 \(|x_i'|+|y_i'| \le k\)。求最小的非负整数天数 \(m\),使得小 X 在季风和自身移动的共同作用下,从 \((0,0)\) 到达 \((x,y)\)
即寻找最小的 \(m \ge 0\),使得:

  1. \(\sum\limits_{i=0}^{m-1} (x_i' + x_{i \bmod n}) = x\)
  2. \(\sum\limits_{i=0}^{m-1} (y_i' + y_{i \bmod n}) = y\)
  3. \(\forall 0 \le i \lt m, \ |x_i'|+|y_i'| \le k\)

\(T \ (1 \le T \le 5 \times 10^4)\) 组测试数据,保证 \(\sum n \le 10^6\)\(0 \le |x|,|y|,|x_i|,|y_i|,k \le 10^8\),坐标和 \(k\) 均为整数,移动步长 \(x_i', y_i'\) 为任意实数。

解题思路

\(m=qn+r\),其中 \(0 \le r \lt n\) 为余数,\(q \ge 0\)。前 \(m\) 天季风带来的总位移为 \(W_x(m) = q \sum\limits_{i=0}^{n-1} x_i + \sum\limits_{i=0}^{r-1} x_i\)\(W_y(m) = q \sum\limits_{i=0}^{m-1} y_i + \sum\limits_{i=0}^{r-1} y_i\)

\(S_x, S_y\) 为一个完整周期的季风位移和,\(P_x(r), P_y(r)\) 为前 \(r\) 天的位移前缀和。则小 X 需要通过自身移动覆盖的剩余位移为 \(\Delta x = x - (q S_x + P_x(r))\)\(\Delta y = y - (q S_y + P_y(r))\)

由于每一天小 X 的移动满足 \(|x_i'|+|y_i'| \le k\),且移动步长为实数,那么在 \(m\) 天内,小 X 能够覆盖的总位移 \((\Delta x, \Delta y)\) 当且仅当 \(|\Delta x| + |\Delta y| \le mk\),即 \(|x - P_x(r) - q S_x| + |y - P_y(r) - q S_y| \le (qn+r)k\)

由于 \(n\) 的范围较小,可以枚举 \(m\)\(n\) 的余数 \(r \in [0,n-1]\)。对于固定的 \(r\),设 \(a=x-P_x(r)\)\(b=y-P_y(r)\),需要找到最小的整数 \(q \ge 0\) 使得 \(|a-q S_x| + |b - q S_y| \le q(nk) + rk\)

为了去掉绝对值符号,需要根据 \(a-qS_x\)\(b-qS_y\) 的正负号将 \(q\) 的取值范围划分为若干区间。

对于固定的 \(r\) 和一组符号 \((s_1,s_2)\),其中 \(s_1,s_2 \in \{1,-1\}\)

  1. 确定 \(q\) 的范围:根据 \(s_1(a-qS_x) \ge 0\)\(s_2(b-qS_y) \ge 0\) 确定 \(q\) 的合法区间 \([L,R]\)
  2. 简化不等式\(s_1(a-qS_x)+s_2(b-qS_y) \le qnk+rk\),整理得 \(s_1a+s_2b-rk \le q(s_1S_x+s_2S_y+nk)\)
  3. 求解线性不等式:设 \(D = s_1S_x + s_2S_y + nk\)\(R' = s_1a+s_2b-rk\)
    • \(D \gt 0\)\(q \ge \lceil R'/D \rceil\),取 \(\max(L,\lceil R'/D \rceil)\) 作为最小的 \(q\)
    • \(D=0\):若 \(R' \le 0\),则 \(q=L\) 为最小解;否则该区间无解。
    • \(D \lt 0\)\(q \le \lfloor R'/D \rfloor\),若 \(L \le \lfloor R'/D \rfloor\),则 \(q=L\) 为最小解;否则无解。

对于每组测试数据,需要枚举 \(r \in [0,n-1]\),每个 \(r\) 内部处理 4 种符号模式,总时间复杂度为 \(O \left( \sum n \right)\)

参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
const ll INF = 4e18;
ll px[N], py[N];
ll myfloor(ll a, ll b) {
    if (b < 0) {
        a = -a; b = -b;
    }
    if (a >= 0) return a / b;
    return (a - b + 1) / b;
}
ll myceil(ll a, ll b) {
    if (b < 0) {
        a = -a; b = -b;
    }
    if (a >= 0) return (a + b - 1) / b;
    return a / b;
}
// sign*(a-q*s)>=0
bool calc(int sign, ll a, ll s, ll& low, ll& high) {
    if (s == 0) {
        if (sign == 1 && a < 0) return false;
        if (sign == -1 && a > 0) return false;
        return true;
    }
    if (sign == 1) { // a-q*s>=0 -> q*s<=a
        if (s > 0) {
            if (a < 0) return false;
            high = min(high, myfloor(a, s));
        } else { // s<0: q*s<=a -> q>=a/s
            low = max(low, myceil(a, s));
        }
    } else { // a-q*s<=0 -> q*s>=a
        if (s > 0) {
            low = max(low, myceil(a, s));
        } else { // s<0: q*s>=a -> q<=a/s
            high = min(high, myfloor(a, s));
        }
    }
    return true;
}
void solve() {
    int n, k, x, y;
    scanf("%d%d%d%d", &n, &k, &x, &y);
    for (int i = 1; i <= n; i++) {
        int xi, yi; 
        scanf("%d%d", &xi, &yi);
        px[i] = px[i - 1] + xi;
        py[i] = py[i - 1] + yi;
    }
    ll sx = px[n], sy = py[n];
    ll ans = INF;
    for (int r = 0; r < n; r++) {
        ll a = x - px[r], b = y - py[r];
        ll best = INF;
        // |a - q*sx| + |b - q*sy| <= qnk + rk
        for (int s1 : {-1, 1}) {
            for (int s2 : {-1, 1}) {
                ll low = 0, high = INF;
                // s1*(a-q*sx)>=0
                if (!calc(s1, a, sx, low, high)) continue;
                // s2*(b-q*sy)>=0
                if (!calc(s2, b, sy, low, high)) continue;
                if (low > high) continue;
                ll d = s1 * sx + s2 * sy + 1ll * n * k;
                ll r1 = s1 * a + s2 * b - 1ll * r * k;
                if (d > 0) {
                    ll q = myceil(r1, d);
                    if (q > high) continue;
                    if (q < low) q = low;
                    best = min(best, q * n + r);
                } else if (d == 0) {
                    if (r1 <= 0) {
                        best = min(best, low * n + r);
                    }
                } else { // d<0: q<=r1/d
                    if (myfloor(r1, d) < low) continue;
                    best = min(best, low * n + r);
                }
            }
        }
        ans = min(ans, best);
    }
    if (ans == INF) printf("-1\n");
    else printf("%lld\n", ans);
}
int main()
{
    int t; scanf("%d", &t);
    while (t--) solve();
    return 0;
}
posted @ 2026-06-10 17:34  RonChen  阅读(4)  评论(0)    收藏  举报