[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的等价类个数

并且显然如果(i1m) mod k=(i2m) mod k(i_1*m)\ mod\ k=(i_2*m)\ mod\ k,那么i1i_1i2i_2等价

(这样的话i1i_1i2i_2无论接受到什么信息都会转移到同一个状态上)

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

讨论n和k不互质的情况

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

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

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) mod Kdh(i)\ mod\ \frac{K}{d}循环

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

所以每一轮

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

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

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

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

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

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

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

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

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

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

用long long乘容易出界。

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

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

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

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

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

DFA最小化就这么完成了(

每组数据复杂度O(log k)O(log\ 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;
}

好久以前写的东西啦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章