CF1184A3 Heidi Learns Hashing (Hard)

发布时间 2023-07-26 17:15:42作者: Ender_32k

https://www.cnblogs.com/Ender32k/p/17582944.html

\(c_i={w_1}_i-{w_2}_i\),相当于找到 \((r,P)\),满足:

\[\sum\limits_{i=0}^nc_ir^i\equiv 0 \pmod P \]

把这个东西写成多项式形式,令 \(f(x)=\sum\limits_{i=0}^nc_ix^i\),即找到一个 \((r,P)\),满足 \(P\mid f(r)\)

由于 \(P\) 的下限较小,考虑随机一个 \(P\in [m,10^6]\),对于每一个 \(r\in [2,P-2]\),判定是否满足 \(P\mid f(r)\)

如果把 \(f(2),f(3),\cdots f(P)\) 看作随机数的话,找不到解的概率为 \((1-\frac{1}{P})^P\sim e^{-1}\ge \frac{1}{3}\),所以我们期望尝试 \(\alpha=3\)\(P\) 就能找出一个解。其中 \(\alpha\) 为常数。

但是此时对于每一个 \(r\) 单独判断的复杂度是 \(O(\alpha n^2)\) 的,虽然利用任意模数多项式多点求值可以做到 \(O(\alpha n\log^2 n)\),但是官方题解说:

This is indeed enough to solve the problem, however it is certainly not the simplest, nor the most elegant ?, way to do that.

官方说这个做法既不简单也不优雅。然而官方题解给出了一个十分优雅的做法:

由欧拉定理可知,对于 \(r\in \mathbb{Z},P\in \mathbb{N^*}\),若 \(\gcd(r,P)=1\),则 \(r^{\varphi(P)}\equiv 1\pmod P\)。所以存在一个正整数 \(k\) 使得 \(r^k\equiv 1\pmod P\),称最小的 \(k\)\(\delta_P(r)\),即 \(k\)\(r\) 在模 \(P\) 意义下的

注意到 \(r^k\equiv r^{k\ \text{mod}\ \delta(r)}\mod P\),所以令 \(g_{j,\delta(r)}=\sum\limits_{i\equiv j\pmod {\delta(r)}}c_i,0\le j<\delta(r)\),那么有:

\[f(r)=\sum\limits_{i=0}^{\delta(r)-1}g_{i,\delta(r)} \]

由于 \(g\) 的值只和 \(\delta(r)\) 有关,这启示我们对于某些 \(\delta(r)\mid k\),算出 \(g_{i,k}\) 的值,然后就可以 \(O(k)\) 地进行求值了。对于 \(k\)\(r\),我们的多点求值复杂度就变成了 \(O(k^2)\)。而且我们仍认为此时的 \(f(r)\) 是随机的,于是无解的概率为 \((1-\frac{1}{P})^k\),随机大约 \(\frac{P}{k}\) 次即有 \(\left((1-\frac{1}{P})^k\right)^{\frac{P}{k}}=(1-\frac{1}{P})^P\sim \frac{1}{3}\) 的概率无解,此时再多试几个 \(P\) 即可。

问题转换为给定 \(k,P\),如何快速求出 \(k\)\(r_1,r_2,\cdots ,r_k\) 满足 \(\forall 1\le i\le k,\delta(r_i)\mid k\)

首先若 \(\delta(r)\mid k\),那么显然有 \(\delta(r^\alpha)\mid k\)\(\alpha\in \mathbb{N^*}\)

考虑到 \(k\mid (P-1)\),那么如果存在 \(r^{\frac{P-1}{k}}\not\equiv 1\pmod P\),由于 \(r^{P-1}\equiv 1\pmod P\),那么 \(k\) 一定是 \(\delta\left(r^{\frac{P-1}{k}}\right)\) 的倍数。那么令 \(r_1=r^{\frac{P-1}{k}},r_2=r^{\frac{2(P-1)}{k}},\cdots ,r_i=r^{\frac{i(P-1)}{k}}\) 即可。

为了让 \(r_i\) 不重复,\(k\) 取质数即可。此时 \(\delta(r_i)=k\),需要特判 \(r_1=1\)\(r_1=P-1\) 的情况。

至此,一个基本简单的做法已经成型:

  • 枚举 \(k\in \mathbb{P}\),构造一个质数 \(P\ge m\),满足 \(k\mid (P-1)\)
  • \(\{1,2,\cdots p-1\}\) 中随机一个 \(r\),满足 \(r^{\frac{P-1}{k}}\not\equiv 1\pmod P\)
  • \(r_i=r^i\),对于 \(1\le i\le k\)\(O(k^2)\) 求出其点值 \(f(r_i)\),若存在 \(f(r_i)=0\)\((r_i,P)\) 为合法的答案。

复杂度我不会算,但官方题解说期望尝试 \(O(\log P)\) 次就可以找到想要的解。

// Problem: Heidi Learns Hashing (Hard)
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1184A3
// Memory Limit: 250 MB
// Time Limit: 5000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;

namespace vbzIO {
    char ibuf[(1 << 20) + 1], *iS, *iT;
    // #if ONLINE_JUDGE
    // #define gh() (iS == iT ? iT = (iS = ibuf) + fread(ibuf, 1, (1 << 20) + 1, stdin), (iS == iT ? EOF : *iS++) : *iS++)
    // #else
    #define gh() getchar()
    // #endif
    #define mt make_tuple
    #define mp make_pair
    #define fi first
    #define se second
    #define pc putchar
    #define pb emplace_back
    #define ins insert
    #define era erase
    typedef tuple<int, int, int> tu3;
    typedef pair<int, int> pi;
    inline int rd() {
        char ch = gh();
        int x = 0;
        bool t = 0;
        while (ch < '0' || ch > '9') t |= ch == '-', ch = gh();
        while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = gh();
        return t ? ~(x - 1) : x;
    }
    inline void wr(int x) {
        if (x < 0) x = ~(x - 1), pc('-');
        if (x > 9) wr(x / 10);
        pc(x % 10 + '0');
    }
}
using namespace vbzIO;

mt19937 rnd(time(0));
const int N = 1e5 + 100;
const int M = 1e6 + 100;

int n, m, ct, pr[M], vs[M], c[M], s[M];
char s1[N], s2[N];

void init(int lim) {
	vs[1] = 1;
	for (int i = 2; i <= lim; i++) {
		if (!vs[i]) pr[++ct] = i;
		for (int j = 1; j <= ct && i * pr[j] <= lim; j++) {
			vs[i * pr[j]] = 1;
			if (i % pr[j] == 0) break;
		}
	}
}

int qpow(int p, int q, int P) {
	int res = 1;
	for (; q; q >>= 1, p = 1ll * p * p % P)
		if (q & 1) res = 1ll * res * p % P;
	return res;
}

int f(int d, int x, int P) {
	int w = 0, b = 1;
	for (int i = 0; i < d; i++)
		(w += 1ll * b * s[i] % P) %= P,
		b = 1ll * b * x % P;
	return w;
}

void solve(int d) {
	for (int i = 0; i < d; i++) s[i] = 0;
	for (int i = 0; i < n; i++) s[i % d] += c[i];
	for (int p = (m / d + 1) * d + 1; p <= 1e6; p += d) {
		if (vs[p]) continue;
		int _r = rnd() % (p - 2) + 1, tp = qpow(_r, (p - 1) / d, p);
		while (tp == 1 || tp == p - 1) _r = rnd() % (p - 2) + 1, tp = qpow(_r, (p - 1) / d, p);
		_r = tp;
		for (int t = 1, r = _r; t <= d; t++, r = 1ll * r * _r % p) {
			if (r == 1 || r == p - 1) break;
			if (!f(d, r, p)) wr(p), pc(' '), wr(r), pc(' '), exit(0);
		}
	}
}

int main() {
    n = rd(), m = rd(), init(1e6);
    scanf("%s%s", s1, s2);
    for (int i = 0; i < n; i++) c[i] = s1[i] - s2[i];
    for (int i = 2; i <= ct; i++) solve(pr[i]);
    return 0;
}