题面
题解
题意:让你构造一个不超过 n + 1 n+1 n+1 个状态的自动机,使得 1 ∼ n 1\sim n 1∼n 的 n ! n! n! 个排列中只有 q q q 个被该自动机接受。
q = n ! q=n! q=n! 可以先特判掉。
然后找到第 q + 1 q+1 q+1 小的排列 p p p,那么我们可以让这个自动机只接受比 p p p 小的排列。那么我们现在要构造的自动机就变成了一个判断字典序的自动机了。
容易想到一种 n + 3 n+3 n+3 个状态的构造方法:
设 a 0 , a 1 , ⋯ , a n , w , l a_0,a_1,\cdots,a_n,w,l a0,a1,⋯,an,w,l,其中 w w w 称作 “胜利状态”, l l l 称作 “失败状态”, a 0 a_0 a0 是起始状态。
接下来是如何设置转移:
- 对于任意的
0
≤
i
<
n
0\leq i< n
0≤i<n,我们考虑状态
a
i
a_i
ai 的转移
δ
(
a
i
,
j
)
\delta(a_i,j)
δ(ai,j):
- 若 1 ≤ j < p i + 1 1\leq j< p_{i+1} 1≤j<pi+1,那么我们令 δ ( a i , j ) ← w \delta(a_i,j)\gets w δ(ai,j)←w。
- 若 j = p i + 1 j=p_{i+1} j=pi+1,那么我们令 δ ( a i , j ) ← a i + 1 \delta(a_i,j)\gets a_{i+1} δ(ai,j)←ai+1。
- 若 p i + 1 < j ≤ n p_{i+1}<j\leq n pi+1<j≤n,那么我们令 δ ( a i , j ) ← l \delta(a_i,j)\gets l δ(ai,j)←l。
- 对于 w w w 的转移,我们令所有的 δ ( w , j ) ← w \delta(w,j)\gets w δ(w,j)←w( 1 ≤ j ≤ n 1\leq j\leq n 1≤j≤n)。
- 对于 l l l 的转移,我们也令所有的 δ ( l , j ) ← l \delta(l,j)\gets l δ(l,j)←l( 1 ≤ j ≤ n 1\leq j\leq n 1≤j≤n)。
- 对于 a n a_n an 的转移,你想怎么连就怎么连,因为你发现自动机读入任意一个 1 ∼ n 1\sim n 1∼n 的排列都不会经过 a n a_n an 的转移。
然后我们只设置 w w w 为接受状态。
容易发现按这个自动机就是模拟了我们一位一位比较字典序的过程。如果读入的排列 p ′ < p p'<p p′<p,最后则会结束在胜利状态 w w w;如果 p ′ = p p'=p p′=p,则会结束在 a n a_n an;如果 p ′ > p p'>p p′>p,则会结束在失败状态 l l l。
接下来考虑如何优化状态数。
首先发现 “区分 p ′ = p p'=p p′=p 还是 p ′ > p p'>p p′>p” 是在做无用功(因为它们都不被接受),而且 a n a_n an 向外的转移时闲置着的,所以我们考虑将 l l l 与 a n a_n an 合并。然后对于 a n a_n an 的转移,我们令所有的 δ ( a n , j ) ← a n \delta(a_n,j)\gets a_n δ(an,j)←an( 1 ≤ j ≤ n 1\leq j\leq n 1≤j≤n)。这样,如果读入的排列 p ′ ≥ p p'\geq p p′≥p,则会结束在 a n a_n an。那么,如果读入的排列 p ′ ≥ p p'\geq p p′≥p,最后则都会结束在 a n a_n an。
那么状态数降到了 n + 2 n+2 n+2。
再想了一会发现单从自动机的结构上好像没有什么可优化的了。
看回题面:自动机要读入的是 1 ∼ n 1\sim n 1∼n 的一个排列。
那么,如果读入排列 p ′ p' p′ 时,发现 p ′ p' p′ 的前 n − 1 n-1 n−1 位都和 p p p 相同,那么就可以判断 p ′ p' p′ 和 p p p 是相同的了。
那么 a n − 1 a_{n-1} an−1 向外转移时只会走到 a n a_n an,于是可以将 a n − 1 a_{n-1} an−1 和 a n a_n an 合并。
状态数成功降到 n + 1 n+1 n+1。
构造题还是做少了……
代码如下:
#include<bits/stdc++.h>
#define N 15
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
int n,q,a[N];
int fac[N];
bool vis[N];
int main()
{
n=read(),q=read()+1;
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i;
if(q>fac[n])
{
puts("1");
for(int i=1;i<=n;i++)
printf("1 ");
puts("\n1");
puts("1 1");
return 0;
}
for(int i=1;i<=n;i++)
{
int t=(q-1)/fac[n-i]+1;
q-=(t-1)*fac[n-i];
for(int j=1,tot=0;j<=n;j++)
{
if(vis[j]) continue;
tot++;
if(tot==t)
{
a[i]=j;
vis[j]=1;
break;
}
}
}
//失败n号,胜利n+1号
printf("%d\n",n+1);
for(int i=1;i<n;i++)
{
for(int j=1;j<=n;j++)
{
if(j<a[i]) printf("%d ",n+1);
if(j==a[i]) printf("%d ",i+1);
if(j>a[i]) printf("%d ",n);
}
puts("");
}
for(int i=1;i<=n;i++)
printf("%d ",n);
puts("");
for(int i=1;i<=n;i++)
printf("%d ",n+1);
puts("\n1");
printf("1 %d\n",n+1);
return 0;
}
/*
2 1
*/
/*
3 2
*/