1. 程式人生 > 其它 >21.4.21 t1

21.4.21 t1

tag:構造


題意

設計一個確定性有限狀態自動機,使得恰好能接受1~n的全排列中的 \(q\)

\(n\leq12,0\leq q\leq n!\)

輸出

第一行為狀態數 \(Q(Q\le n+1)\)

接下來 \(Q\) 行,每行 \(n\) 個數。第 \(i\) 行第 \(j\) 個數 \(a_{i,j}\) 表示第 \(i\) 個狀態在遇到 \(j\) 時會轉移到狀態 \(a_{i,j}\)

接下來一行一個數 \(q_0\) 表示初始狀態為 \(q_0\)

接下來一行若干個數,第一個數為接受的狀態數 \(n\),後面 \(n\) 個不同整數\(f_1,f_2\cdots f_n\) 表示接受狀態 \(f_1,f_2\cdots f_n\)


實際上可以構造出自動機恰好接受從 \(1,2\cdots n\) 一直到第 \(q\) 個排列。

將第 \(n\) 個狀態當作“垃圾桶”,第 \(n+1\) 個狀態當作"終點"。這兩個狀態都是自己連自己。

舉個例子就很明顯了。\(n=4\),接受所有小於 \(3,2,4,1\) 的排列。

拆分一下:

  • 接受所有以 \((1)\)\((2)\) 開頭的排列

\((1\to n+1,1/2),\ (1\to2,3)\ (1\to n,4)\)

  • 接受所有以 \((3,1)\) 開頭的排列

\((2\to n+1,1),\ (2\to3,2)\ (2\to,n,3/4)\)

  • 接受所有以 \((3,2,1)\)
    \((3,2,4)\) 開頭的排列

\((3\to n+1,1/4),\ (3\to n, 2/3)\)


思路就是逐位限定。當前狀態向終點 \(n+1\) 的連邊就是當前位接受的狀態,然後再向下一個狀態連一條邊,其餘的邊都往垃圾桶 \(n\) 連。


#include<bits/stdc++.h>
using namespace std;
 
template<typename T>
inline void Read(T &n){
    char ch; bool flag=false;
    while(!isdigit(ch=getchar()))if(ch=='-')flag=true;
    for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
    if(flag)n=-n;
}

int n, q;

char vis[15];
int ans[15][15];

int getnxt(int x){x++;while(vis[x])x++;return x;}

int main(){
    // freopen("1.out","w",stdout);
    Read(n); Read(q);
    int tp = 1;
    vis[0] = true;
    for(register int i=1; i<n; i++) tp *= i;
    printf("%d\n",n+1);
    for(register int i=1; i<n; i++){
        int k = q/tp, x=0; q %= tp;
        while(k--) x=getnxt(x), ans[i][x] = n+1;
        vis[x=getnxt(x)] = true;
        ans[i][x] = i+1;
        tp /= (n-i);
    }
    for(register int i=1; i<=n; i++,puts("")) for(register int j=1; j<=n; j++)
        printf("%d ",ans[i][j]?ans[i][j]:n);
    for(register int i=1; i<=n; i++) printf("%d ",n+1);puts("");
    puts("1");
    printf("1 %d\n",n+1);
    return 0;
}