题面
题解
题意:让你构造一个不超过 \(n+1\) 个状态的自动机,使得 \(1\sim n\) 的 \(n!\) 个排列中只有 \(q\) 个被该自动机接受。
\(q=n!\) 可以先特判掉。
然后找到第 \(q+1\) 小的排列 \(p\),那么我们可以让这个自动机只接受比 \(p\) 小的排列。那么我们现在要构造的自动机就变成了一个判断字典序的自动机了。
容易想到一种 \(n+3\) 个状态的构造方法:
设 \(a_0,a_1,\cdots,a_n,w,l\),其中 \(w\) 称作 “胜利状态”,\(l\) 称作 “失败状态”,\(a_0\) 是起始状态。
接下来是如何设置转移:
- 对于任意的 \(0\leq i< n\),我们考虑状态 \(a_i\) 的转移 \(\delta(a_i,j)\):
- 若 \(1\leq j< p_{i+1}\),那么我们令 \(\delta(a_i,j)\gets w\)。
- 若 \(j=p_{i+1}\),那么我们令 \(\delta(a_i,j)\gets a_{i+1}\)。
- 若 \(p_{i+1}<j\leq n\),那么我们令 \(\delta(a_i,j)\gets l\)。
- 对于 \(w\) 的转移,我们令所有的 \(\delta(w,j)\gets w\)(\(1\leq j\leq n\))。
- 对于 \(l\) 的转移,我们也令所有的 \(\delta(l,j)\gets l\)(\(1\leq j\leq n\))。
- 对于 \(a_n\) 的转移,你想怎么连就怎么连,因为你发现自动机读入任意一个 \(1\sim n\) 的排列都不会经过 \(a_n\) 的转移。
然后我们只设置 \(w\) 为接受状态。
容易发现按这个自动机就是模拟了我们一位一位比较字典序的过程。如果读入的排列 \(p'<p\),最后则会结束在胜利状态 \(w\);如果 \(p'=p\),则会结束在 \(a_n\);如果 \(p'>p\),则会结束在失败状态 \(l\)。
接下来考虑如何优化状态数。
首先发现 “区分 \(p'=p\) 还是 \(p'>p\)” 是在做无用功(因为它们都不被接受),而且 \(a_n\) 向外的转移时闲置着的,所以我们考虑将 \(l\) 与 \(a_n\) 合并。然后对于 \(a_n\) 的转移,我们令所有的 \(\delta(a_n,j)\gets a_n\)(\(1\leq j\leq n\))。这样,如果读入的排列 \(p'\geq p\),则会结束在 \(a_n\)。那么,如果读入的排列 \(p'\geq p\),最后则都会结束在 \(a_n\)。
那么状态数降到了 \(n+2\)。
再想了一会发现单从自动机的结构上好像没有什么可优化的了。
看回题面:自动机要读入的是 \(1\sim n\) 的一个排列。
那么,如果读入排列 \(p'\) 时,发现 \(p'\) 的前 \(n-1\) 位都和 \(p\) 相同,那么就可以判断 \(p'\) 和 \(p\) 是相同的了。
那么 \(a_{n-1}\) 向外转移时只会走到 \(a_n\),于是可以将 \(a_{n-1}\) 和 \(a_n\) 合并。
状态数成功降到 \(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
*/
标签:状态,排列,XSY3993,构造,leq,读入,delta,自动机
From: https://www.cnblogs.com/ez-lcw/p/16842959.html