传送门:bzoj5213
题解
问题的本质是,计算能识别所有 m m m进制下 k k k的倍数的确定性有限状态自动机(DFA)的最小点数。— 官方题解
首先存在答案上界 k k k:构造 k k k个点(标号为 [ 0 , k ) [0,k) [0,k)),第 i i i点依次代表 m o d k = i mod\ k=i mod k=i,第 i i i个点的第 j j j条出边连向 i ⋅ m + j m o d    k i·m+j \mod k i⋅m+jmodk。
答案就是上述自动机节点的等价类个数。点 i , j i,j i,j等价当且仅当 i i i出发能接受的所有 m m m进制数集合和 j j j完全相同。
设 f ( i ) f(i) f(i)表示从点 i i i出发,能接受的最短的 m m m进制数的位数, g ( i ) = i ⋅ m f ( i ) ( m o d k ) g(i)=i·m^{f(i)}\pmod k g(i)=i⋅mf(i)(modk)。
则点 i , j i,j i,j等价的充要条件即 f ( i ) = f ( j ) f(i)=f(j) f(i)=f(j)且 g ( i ) = g ( j ) g(i)=g(j) g(i)=g(j)(不会证明)。
-
0 0 0必然是单独的一个等价类,设 f ( l , m , k ) f(l,m,k) f(l,m,k)表示在制定的 m , k m,k m,k下还剩下 1 , 2 , . . . , l 1,2,...,l 1,2,...,l的等价类个数。
-
把这些数 × m m o d k \times m \ mod \ k ×m mod k,若两个数操作后相等,则它们必然在一个等价类中,直接去重。设 d = g c d ( m , k ) d=gcd(m,k) d=gcd(m,k),则所有数均为 d d d的倍数。
-
此时所有 > k − m >k-m >k−m的数和 0 0 0都分别代表一个等价类, f f f都 = 1 =1 =1,把这些数删去。
-
对于剩下的数,再 × m m o d k \times m \ mod \ k ×m mod k,去重后删去 > k − m 2 >k-m^2 >k−m2的数,依次类推。
具体来说,设初始状态为 f ( k − 1 , m , k ) f(k-1,m,k) f(k−1,m,k),每一层迭代删去 f f f逐次 + 1 +1 +1的等价类:
- 设 d = g c d ( m , k ) d=gcd(m,k) d=gcd(m,k),若 d = 1 d=1 d=1,显然答案为 l l l,否则将 [ 1 , l ] [1,l] [1,l] × m m o d k \times m \ mod \ k ×m mod k
- 上一层删去了
(
l
,
k
)
(l,k)
(l,k)中的数,则这一轮中要删去在
(
k
−
m
(
k
−
l
)
,
k
)
∪
{
0
}
(k-m(k-l),k)\cup \{0\}
(k−m(k−l),k)∪{0}中的数:
( 1 ) (1) (1) 若 k ≤ m ( k − l ) k\leq m(k-l) k≤m(k−l),显然答案为 l l l
( 2 ) (2) (2) 若 l > k d l>\frac kd l>dk,则剩下的是 ( 0 , k − m ( k − l ) ] (0,k-m(k-l)] (0,k−m(k−l)]中 d d d的倍数,删去了 m ( k − l ) d \frac{m(k-l)}{d} dm(k−l)个数,递归子问题 f ( k − m ( k − l ) d , m , k d ) f(\frac{k-m(k-l)}{d},m,\frac{k}{d}) f(dk−m(k−l),m,dk)
( 3 ) (3) (3) 若 l ≤ k d l\leq \frac kd l≤dk,则 l ≤ k d ⋅ d ( m − 1 ) m l\leq \frac kd ·\frac{d(m-1)}{m} l≤dk⋅md(m−1),则 k ≤ m ( k − l ) k\leq m(k-l) k≤m(k−l),转成情况 ( 1 ) (1) (1)
总时间复杂度 O ( T log k ) O(T\log k) O(Tlogk)
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int T;ll m,k;
inline ll gcd(ll x,ll y){return (!y)?x:gcd(y,x%y);}
ll sol(ll l,ll k)
{
ll d=gcd(m,k);if(d==1 || l<=k/d) return l;
if(k<=(double)m*(k-l)) return k/d;
return m/d*(k-l)+sol((k-m*(k-l))/d,k/d);
}
int main(){
for(scanf("%d",&T);T;--T){
scanf("%lld%lld",&m,&k);
printf("%lld\n",sol(k-1,k)+1);
}
return 0;
}