[ZJOI2018]迷宫
复盘 cdw 讲的题,写篇题解来祸害社会(
Description
给定 \(m\) 和 \(k\) ,要求构造这样的一张图,满足以下条件:
-
每个点恰好有 \(m\) 条出边,每个出边都恰好有一个转移值 \(\in [0, m)\) 。
-
从初始点开始,走一条长度为 \(k\) 的路径,并且回到出发节点,路径上所有转移构成的一个 \(m\) 进制的数恰为 \(k\) 的倍数。
-
图无限制,可有自环和重边。
要求一个总点数最少的图,求这个点数 \(n\) 。
多测。
\(1\leq m,\ k \leq 10 ^ {18},\ 1\leq t \leq 3 \cdot 10 ^ 5\)
Solution
能明显感觉到这玩意的定义有点像 DFA ,但是用处不大所以我们不去管他。
n = k
(注:初始节点定为 \(0\) ,对于状态表示更方便
先满足第一个限制,这样就有一个有着无限个点的 \(m\) 叉树,每条边能够按照题目的意思分配好转移值,那么每个点对应的状态就是从初始状态到自己的路径组成的 \(m\) 进制数。
形式化表达就是对于一个点对应的状态 \(u\) ,儿子的状态为 \(u \cdot m + i,\ i \in [0,\ m)\)
考虑怎么一步到位,满足第二个限制,我们知道,如果要求一个数是 \(k\) 的倍数,那么在树上的 \(m\) 个儿子在模 \(k\) 意义下本质上还是一样的,那么我们就直接考虑对所有状态的值模 \(k\) 。
恰好所有 \(k\) 的倍数最终会连回初始节点同时因为有限制三,我们无论如何都不会连的不合法,所以这样我们就一次性得到了一个点数为 \(k\) 的满足条件的图。
n < k
这里同 SAM 一样,我们要求的是一个点数尽量少的图,所以我们也要考虑考虑缩点的问题。
假如两个点 \(a\) 和 \(b\) 能被缩到一起,那么一定有:
- \(\forall\ i \in [0,\ m),\ (a \cdot m + i) \equiv (b \cdot m + i)\ ({\rm mod}\ k)\)
或者
- \(\forall\ i \in [0,\ m),\ (a \cdot m + i) \equiv b\ ({\rm mod}\ k)\)
再或者
- \(\forall\ i \in [0,\ m),\ (b \cdot m + i) \equiv a\ ({\rm mod}\ k)\)
形式化理解就是要么这两个状态全部指向同样的点,要么无论从哪个点进入状态 \(1\) ,它都会给你怼到状态 \(2\) 去,这样的话明显我们不需要建立两个点,就可以缩掉。
其实我们还可以进一步感受到一个地方就是上文提到的后两种情况,因为 \(i\) 是连续的整数,所以只有可能是 \(k = 1\) 的情况,这种情况又只有 \(0\) 它自己,所以说可以忽略。
所以我们就只有这样的一个限制:
此时 \(m\) 和 \(k\) 可能不互质,我们令 \(g = \gcd(m, k),\ k^{'} = k / g,\ m^{'} = m / g\) ,有。
此时则必然有 \(a \equiv b\ ({\rm mod}\ k ^ {'})\) ,具体证明(其实不是很重要,意会意会就行了):
使 \(a\) 和 \(b\) 能表示成: \(a = i \cdot k ^ {'} + x,\ b = j \cdot k ^ {'} + y\) 其中 \(x\) 和 \(y\) 均小于 \(k ^ {'}\) 。
则有: \(a \equiv x\ ({\rm mod}\ k ^ {'}),\ b \equiv y\ ({\rm mod}\ k ^ {'})\) 。
同时也有:\(a \cdot m ^ {'} \equiv (i \cdot k ^ {'} + x) \cdot m ^ {'} \equiv x \cdot m ^ {'}\ ({\rm mod}\ k ^ {'}),\ b \cdot m ^ {'} \equiv (j \cdot k ^ {'} + y) \cdot m ^ {'} \equiv y \cdot m ^ {'}\ ({\rm mod}\ k ^ {'})\)
所以:\(x \cdot m ^ {'} \equiv y \cdot m ^ {'}\ ({\rm mod}\ k ^ {'})\)
因为注意到 \(x\) 和 \(y\) 的范围不超过 \(k ^ {'}\) ,所以有:\(x \equiv y\ ({\rm mod}\ k ^ {'})\)
所以:\(a \equiv b\ ({\rm mod}\ k ^ {'})\)
所以很明显在新的模 \(k ^ {'}\) 的意义下相同的数都要合并到一起,然后你手模了三个样例,发现全部满足,然后就结束了吗??
当 \(m = 2,\ k = 8\) 的时候,手摸完发现点数变成了 \(5\) 个,但是似乎还可以继续合并成 \(4\) 个点。但是在刚刚的步骤中仅仅只操作了一次,所以这个做法并不完美。
那怎么处理能够多次缩点的情况呢??
其实我们能感觉到这有点子问题的意思,在这些缩完的点中,其实就是在新的 \(k ^ {'}\) 之下一个新的图,所以按照之前的做法,我们可以总结出:\(a \cdot m^p \equiv b \cdot m^p\ ({\rm mod}\ k^{'})\) ,而每次我们只需要把新的 \(k\) 传下去做递归,直到不能再缩为止。
注意到可能相乘会爆 long long ,所以乘化除就行了。
Code
/*
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll m, k;
inline ll read() {
char ch = getchar();
ll s = 0, w = 1;
while (!isdigit(ch)) {if (ch == '-') w = -1; ch = getchar();}
while (isdigit(ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar();}
return s * w;
}
inline ll gcd(ll a, ll b) {return (!b) ? a : gcd(b, a % b);}
inline ll solve(ll now, ll t, ll k) {
ll g = gcd(m, k), _k = k / g, _m = m / g;
if (g == 1 || now <= _k) return 0;
if ((double)_k / _m < t) return now - _k;
t *= _m;
return solve(_k - t, t, _k) + now - _k;
}
inline void mian() {
m = read(); k = read();
printf("%lld\n", k - solve(k - 1, 1, k));
}
int main() {
int T = read();
while (T--) mian();
return 0;
}