【XSY3993】自动机(构造)

题面

自动机

题解

题意:让你构造一个不超过 n + 1 n+1 n+1 个状态的自动机,使得 1 ∼ n 1\sim n 1n 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 0i<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} 1j<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<jn,那么我们令 δ ( 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 1jn)。
  • 对于 l l l 的转移,我们也令所有的 δ ( l , j ) ← l \delta(l,j)\gets l δ(l,j)l 1 ≤ j ≤ n 1\leq j\leq n 1jn)。
  • 对于 a n a_n an 的转移,你想怎么连就怎么连,因为你发现自动机读入任意一个 1 ∼ n 1\sim n 1n 的排列都不会经过 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 1jn)。这样,如果读入的排列 p ′ ≥ p p'\geq p pp,则会结束在 a n a_n an。那么,如果读入的排列 p ′ ≥ p p'\geq p pp,最后则都会结束在 a n a_n an

那么状态数降到了 n + 2 n+2 n+2

再想了一会发现单从自动机的结构上好像没有什么可优化的了。

看回题面:自动机要读入的是 1 ∼ n 1\sim n 1n 的一个排列。

那么,如果读入排列 p ′ p' p 时,发现 p ′ p' p 的前 n − 1 n-1 n1 位都和 p p p 相同,那么就可以判断 p ′ p' p p p p 是相同的了。

那么 a n − 1 a_{n-1} an1 向外转移时只会走到 a n a_n an,于是可以将 a n − 1 a_{n-1} an1 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
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值