同余方程有解的条件 && Cyclic Cipher题解

一.结论

对于 \(n\) 个同余方程组,如果两两有解,则一定有解满足 \(n\) 个方程组。

证明:(归纳法)

1.初始状态

attention:这里(初始状态部分)的 \([]\) 都表示 \(lcm\), \(()\) 都表示 \(gcd\),就不大写了


我们先只考虑三个同余方程组.

假设三个同余方程组:(条件)

\[\begin{cases} Q \equiv a_1 \pmod {m_1} \\ Q \equiv a_2 \pmod {m_2} \\ Q \equiv a_3 \pmod{m_3} \end{cases} \]

由中国剩余定理,有以下基本结论:

\[\begin{cases} (m_1, m_2) | a_1 - a_2 \\ (m_1, m_3) | a_1 - a_3 \\ (m_2, m_3) | a_2 - a_3 \end{cases} \]


引理1:

\[([a, b], c) = [(a, c), (b,c)] \]

考虑质因子 \(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:

\[\begin{cases} a | b \\ b |c \end {cases} \Rightarrow a | c \]

整除的传递性


引理3:

\[\begin{cases} a | c \\ b |c \end {cases} \Rightarrow [a, b] | c \]

考虑质因子 \(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\)

  1. 如果发现第 \(r\) 和第 \(r + 1\) 不相邻,则 \(l = r\)

因为都不连续了,所以 \(l\) 可以直接赋为 \(r\)

  1. 长度(即模数)最大为 \(40\),所以方程最多有 \(40\) 个 (如果模数一样,但是余数不一样,则无解)。

我们用 \(STL\) 保存下来所有同余方程,每次 \(r++\)

假设我们现在的 \(r\)对应的长度为 \(len\)\(x\) 出现在 \(p\) 号位置。

  1. 如果\(len\) 没出现过,就暴力扫一遍所有的同余方程,看看都能不能和新方程(\(t \equiv p \pmod {len}\))合并,不满足就 \(l++\),直到与之矛盾的方程消失为止。

  2. 如果\(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;
}
posted @ 2021-08-23 18:09  C2022lihan  阅读(525)  评论(0编辑  收藏  举报