【XSY3993】自动机(构造)

题面

自动机

题解

题意:让你构造一个不超过 \(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
*/
posted @ 2022-10-31 07:23  ez_lcw  阅读(49)  评论(0编辑  收藏  举报