同余分析
同余类分析法 的基本思想是:利用同余关系将全体整数(或问题的状态空间)划分为若干个互不相交的等价类。
- 分类标准:选定一个基准数 \(M\),根据每个数 \(x\) 满足 \(x \equiv r \pmod{M}\) 的余数 \(r\) 进行归类。
- 分析逻辑:如果一个性质对于某个余数 \(r\) 成立,那么通过给 \(x\) 不断加减 \(M\)(即在同一个同余类内平移),往往能推导出该类中所有(或大部分)元素的性质。
例题:P3951 [NOIP 2017 提高组] 小凯的疑惑
要求的是最大的表示不出来的价值,那么这个价值加一是能被表示出来的。
如果一个价值 \(n\) 能被表示出来,那么 \(n+b\) 一定可以被表示。
将价值按除以 \(b\) 的余数分成 \(b\) 组,以 \(a=3, \ b=7\) 为例:

除了最后一列,每一列最小的可被表示的价值实际上是 \(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\),使得:
- \(\sum\limits_{i=0}^{m-1} (x_i' + x_{i \bmod n}) = x\)
- \(\sum\limits_{i=0}^{m-1} (y_i' + y_{i \bmod n}) = y\)
- \(\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\}\)。
- 确定 \(q\) 的范围:根据 \(s_1(a-qS_x) \ge 0\) 和 \(s_2(b-qS_y) \ge 0\) 确定 \(q\) 的合法区间 \([L,R]\)。
- 简化不等式:\(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)\)。
- 求解线性不等式:设 \(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;
}

浙公网安备 33010602011771号