[Luogu4339] [ZJOI2018] 迷宫 [有限状态自动机]

参考:
对DFA和NFA的简单理解:http://blog.163.com/ppt_compiler/blog/static/202813007201222873720918/
NFA确定化和DFA最小化:https://blog.csdn.net/u014541281/article/details/52423191
题解:https://blog.csdn.net/qq_16267919/article/details/79675344
https://blog.csdn.net/yfzcsc/article/details/79681391

m进制可行路径%k=0

那么DFA状态上界就是k,分别对应cur%k=[0,k-1]

设k个状态,每个状态有m条出边

状态i的出边j连向状态(im+j)%k

这样的话,只要能够回到0点,这条路径%k就等于0

不过这样构建出来得到的n就不是最小的

所以我们要考虑最小化DFA,那么n就可以最小化,得出答案

最小化DFA需要满足:
1.没有多余的状态
2.没有两个状态是相互等价的

多余状态分两种情况:
1.从这个状态没有通路到达终态
2.从开始状态出发,任何输入串也不能到达的那个状态

两个状态等价需要满足:
1.兼容性(一致性):同是终态或同是非终态
2.传播性(蔓延性):对于所有输入符号,两个状态会转换到等价的状态里
简单解释一下终态和非终态
如果到了状态S,并且没有继续输入了
S为终态,则能够到达NFA终态
S为非终态,不能到达NFA终态
DFA最小化
1.将DFA的状态分为终态和非终态
2.考察每个子集是否再分直到每个子集都不能再分
3.将每一个子集用子集中的某一个状态代替
(注意:如果代替子集的那一个状态有自边,代替之后要保留这个状态的自边;子集中其他状态的互相转化不需要考虑)

所以我们需要得到最小化DFA,就得计算出之前DFA的等价类个数

并且显然如果 ( i 1 ∗ m )   m o d   k = ( i 2 ∗ m )   m o d   k (i_1*m)\ mod\ k=(i_2*m)\ mod\ k (i1m) mod k=(i2m) mod k,那么 i 1 i_1 i1 i 2 i_2 i2等价

(这样的话 i 1 i_1 i1 i 2 i_2 i2无论接受到什么信息都会转移到同一个状态上)

显然地,如果m和k互质,n=k

讨论n和k不互质的情况

由于我们要维护序号为0~n的[0,n)个状态,所以一个等价类得只取最小那个点

0是独立的等价类。所以我们只要对[1,k-1]进行操作

f ( L , K ) f(L,K) f(L,K)

表示这一轮我们在[1,L]中进行删除;K为这一轮的k

也就是说我们上一轮删除了(L,K)的数

以首轮来讲

我们需要对(1,L)中*m%K等价的数进行去重。

剩下的数里(L-m+1,L)的数分别是独立的等价类;

所以答案要加上这些数,然后这些数不会参与下一轮计算。

重复以上过程直到gcd(m,K)=1

我们考虑如何实现f(L,K)的过程。

首先,我们把gcd(m,K)表示成d(

本题最关键的推导↓↓

(虽然我大概会写错什么)


每一轮首先求一下gcd(m,K)如果=1则return L

为了方便表示,记h(i)=i*m mod k

h(i)∈{d|d*j∈[0,K),j∈N}

易得 h ( i )   m o d   K d h(i)\ mod\ \frac{K}{d} h(i) mod dK循环

也就是说h(1)~h( K d ) \frac{K}{d}) dK)取遍了 [   0 , K ) [\,0,K) [0,K)中d的倍数

所以每一轮

首先每个数都要再乘上一次m

如果L> k d \frac{k}{d} dk

乘上m之后,答案是会有重复的;

并且我们已经得知了在上一层有等价类(L,K)

那么这一层就得去掉上一层等价类能推到的 m ( K − L ) d \frac{m(K-L)}{d} dm(KL)个数;

剩下的数除以d得到新的L=(0, K − m ( K − L ) d \frac{K-m(K-L)}{d} dKm(KL)]

注意一下K-m(K-L)可能小于等于0 这个时候我们得返回一个 K d \frac{K}{d} dK

判断K-m(K-L)小于等于0的时候

可以先转成double进行运算 也可以把乘移位一下变成除(当然除会比乘慢。。

因为long long进行乘法运算的范围小于double

用long long乘容易出界。

我们将K更改为 K d \frac{K}{d} dK,继续递归调用f(L,K)

如果L<= K d \frac{K}{d} dK

删掉了所有L个数。这些数要统计到答案里,于是返回L

然后把上面统计的答案,再加上最开始独立的等价类0就得到了我们要的最小n

也就是最小化DFA的状态数量

DFA最小化就这么完成了(

每组数据复杂度 O ( l o g   k ) O(log\ k) O(log k)

总复杂度 O ( T   l o g   k ) O(T\ log\ k) O(T log k)


其实DFA最小化并不难理解

最重要的任务还是理解模型的性质(

有不少自动机的东西就算不扯上自动机这个名词也不难懂的


#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
#define ll long long int
#define getchar() (frS==frT&&(frT=(frS=frBB)+fread(frBB,1,1<<12,stdin),frS==frT)?EOF:*frS++)
char frBB[1<<12],*frS=frBB,*frT=frBB;
inline ll __READ()
{
    ll x=0;char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
    return x;
}
#define read() __READ()
int T;
ll m,k;
ll gcd(ll a,ll b){return !b?a:gcd(b,a%b);}
ll solve(ll L,ll K)
{
    ll d=gcd(m,K);
    if(d==1)return L;
    ll limit=K/d;
    if(L>limit)
    {
        if(K<=1.0*m*(K-L))return limit;
        return m*(K-L)/d+solve((K-m*(K-L))/d,limit);
    }
    return L;
}
int main()
{
    T=read();
    while(T--)
    {
    	m=read(),k=read();
        printf("%lld\n",solve(k-1,k)+1);
    }
    return 0;
}

好久以前写的东西啦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值