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