同余方程有解的条件 && Cyclic Cipher题解
一.结论
对于 \(n\) 个同余方程组,如果两两有解,则一定有解满足 \(n\) 个方程组。
证明:(归纳法)
1.初始状态
attention:这里(初始状态部分)的 \([]\) 都表示 \(lcm\), \(()\) 都表示 \(gcd\),就不大写了
我们先只考虑三个同余方程组.
假设三个同余方程组:(条件)
由中国剩余定理,有以下基本结论:
引理1:
考虑质因子 \(p\), 它在 \(a,b,c\) 中幂次分别为 \(q_a,q_b,q_c\)。
则 \(\begin{cases} ([a, b], c) = \min(\max(q_a,q_b), q_c) ① \\ [(a, c), (b, c)] = \max (\min (q_a,q_c),\min(q_b,q_c)) ② \end{cases}\)
暴力分类讨论 \(q_a, q_b, q_c\) 的相对大小,容易得出 \(① = ②\)。
引理2:
整除的传递性
引理3:
考虑质因子 \(p\),它在 \(a,b,c\) 中幂次分别为 \(q_a,q_b,q_c\)。
则由条件得:\(\begin{cases} q_a \leq q_c \\ q_b \leq q_c \end {cases}\)
则:\(\max (q_a,q_b) \leq q_c\)
则:\([p^{q_a},p^{q_b}] = p^{\max(q_a,q_b)} | p^{q_c}\)
所以 \([p^{q_a},p^{q_b}] | p^{q_c}\)
对于所有的质因子,都有\([p^{q_a},p^{q_b}] | p^{q_c}\),那么 \([\prod p^{q_a},\prod p ^{q_b}] | \prod p ^{q_c}\) 即 \([a, b] | c\)
证明:
令 \(x \equiv a_1 \pmod {m_1}, x \equiv a_2 \pmod {m_2},x \leq [ m_1,m_2]\)
则 \(x = a_1 + m_1y\)
即证:\(\begin{cases} Q \equiv x \pmod {[m_1,m_2]} \\ Q \equiv a_3 \pmod { m_3} \end{cases}\) (扩展中国剩余定理)
即证:\(([m_1,m_2], m_3) | x - a_3\) (扩展中国剩余定理)
即证:\([(m_1, m_3), (m_2, m_3)] | x - a_3\) (引理1)
即证:\(\begin{cases} (m_1, m_3) | x - a_3 \\ (m_2,m_3) | x- a_2 \end{cases}\) (引理3)
我们只考虑 \((m_1, m_3) | x - a_3\),\((m_2, m_3) | x - a_2\) 同理
即证: \((m_1, m_3) | a_1 + m_1y - a_3\)
即证: \((m_1, m_3) | a_1 - a_3 + m_1y\)
即证:\(\begin{cases} (m_1, m_3) | a_1-a_3 \\(m_1, m_3) | m_1y \end{cases}\)
由引理2(整除的传递性):
即证:\(\begin{cases} (m_1, m_3) | a_1-a_3 ① \\(m_1, m_3) | m_1 ② \\ m_1 | m_1y ③\end{cases}\)
①:已证,在基本条件处
②:\(gcd\) 的性质
③:易证
得证。
2.归纳过程
attention: 这里 \([l, r]\) 表示第 \(l\) 个到第 \(r\) 个方程合并后的方程。
\(f(i) = 1\),则表明合并后 \(n - i\) 个方程后,有解。
欲证:若 \(f (i) = 1\) 且方程两两有解, \(f (i + 1) = 1\)。
考虑第 \(k\) 个方程
则:由初始状态的证明,有解同时满足第 \(k\), \(i\), \(i + 1\) 个方程。
由扩展剩余定理,则第 \(i\) 和 \(i + 1\) 个方程合并后,还能与第 \(k\) 个方程合并。
令第 \(i\) 和 \(i + 1\) 个方程合并后的方程为 \(G\),则 \(G\) 和 \(k\) 有解。
所以任意方程都与 \(G\) 有解,所以 \(i\), \(i + 1\) 合并后,如果有解能满足这 \(i\) 个同余方程(\(f (i) = 1\)),则有解能满足这 \(i + 1\) 个方程(\(f (i + 1) = 1\))
二.实现
有了结论,这道题就非常好做了。
因为操作时间非常长,大于 \(2\) 倍 \(lcm\),所以一个周期里的情况都会出现。
枚举最长串的数字是 \(x\),如果在时间 \(t\), \([l, r]\) 这段子串同时出现 \(x\),则 \(t\) 满足 \(\forall t \equiv p_i \pmod {len_i} (i \in [l,r])\) (\(len_i\) 表示第 \(i\) 个序列的长度,\(p_i\) 表示 \(x\) 在第 \(i\) 个序列出现的位置(从零开始编号))。
则我们跑所有 \(x\) 出现的行。
我们考虑用双指针 \(l,r\)。
- 如果发现第 \(r\) 和第 \(r + 1\) 不相邻,则 \(l = r\)。
因为都不连续了,所以 \(l\) 可以直接赋为 \(r\)
- 长度(即模数)最大为 \(40\),所以方程最多有 \(40\) 个 (如果模数一样,但是余数不一样,则无解)。
我们用 \(STL\) 保存下来所有同余方程,每次 \(r++\)。
假设我们现在的 \(r\)对应的长度为 \(len\),\(x\) 出现在 \(p\) 号位置。
-
如果\(len\) 没出现过,就暴力扫一遍所有的同余方程,看看都能不能和新方程(\(t \equiv p \pmod {len}\))合并,不满足就 \(l++\),直到与之矛盾的方程消失为止。
-
如果\(len\) 出现过,就判断新余数和之前出现的模数相同的方程的余数是否相同,不相同就 \(l++\),直到没有模数为 \(len\) 的方程(除了这个新的方程) 。
但是!!!由于 \(Excrt\) 可以判断出余数不同的情况,所以我们就不需要写这个判断了(写了代码也不长)。
时间复杂度均摊下来是: \(O (40 * log_240 * \sum{len_i})\)
三.参考代码
#include <map>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
#define LL long long
#define ULL unsigned long long
#define PII pair <int, int>
#define MP(x,y) (make_pair (x, y))
template <typename T> void read (T &x) { x = 0; T f = 1;char tem = getchar ();while (tem < '0' || tem > '9') {if (tem == '-') f = -1;tem = getchar ();}while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0';tem = getchar ();}x *= f; return; }
template <typename T> void write (T x) { if (x < 0) {x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0'); }
template <typename T> void print (T x, char ch) { write (x); putchar (ch); }
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }
const int Maxn = 1e5;
const int Maxlen = 40;
int n, m;
int len[Maxn + 5];
int a[Maxn + 5][Maxlen + 5];
vector <PII> v[Maxn + 5];
int bak[Maxn + 5], b[Maxn + 5];
LL gcd (LL x, LL y) {
if (y == 0) return x;
else return gcd (y, x % y);
}
bool check (int m1, int b1, int m2, int b2) {
return (b1 - b2) % gcd (m1, m2) == 0;
}
int main () {
read (n); read (m);
for (int i = 1; i <= n; i++) {
read (len[i]);
for (int j = 0; j < len[i]; j++)
{ read (a[i][j]); v[a[i][j]].push_back (MP (i, j)); }
}
for (int i = 1; i <= m; i++) {
memset (bak, 0, sizeof bak);
int l = 0, r = 0, ans = 0;
while (r < (int)v[i].size ()) {
int idx = v[i][r].first, Mod = len[idx], p = v[i][r].second;
if (r != 0 && idx != v[i][r - 1].first + 1)
while (l < r)
bak[len[v[i][l++].first]]--;
for (int j = 1; j <= 40; j++) {
if (bak[j] >= 1 && check (j, b[j], Mod, p) == 0) {
while (bak[j])
{ bak[len[v[i][l++].first]]--; }
}
}
bak[Mod]++;
b[Mod] = p;
ans = Max (ans, r - l + 1);
r++;
}
printf ("%d\n", ans);
}
return 0;
}