[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;
}

好久以前寫的東西啦

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