【补题】The 2023 ICPC Asia Shenyang Regional Contest
B. Turning Permutation
求字典序第 k 小的回转排列(定义见原题)。
既然是考虑排列字典序,那么可以从排列的最高位开始一位一位确定。比如我目前确认了排列的第 1 位为 \(p_1\),那么字典序一定比将构造出来的排列小的排列,第一位就是 2~n。此时通过枚举第1位上的p,就可以求解以每个值为开头的回转排列一共有多少个,再根据 k 确定所求的字典序第 k 小排列在哪个值为开头的回转排列集里面。
由此,如果可以完成固定前缀的回转排列数的快速求解,那么就能确定排列的最高位。确定完最高位之后,又可以枚举下一位的取值,在确定的前缀下求出回转排列数,从而再求出所求排列所在的排列前缀子集。
重新审题:
回转排列:每添加下一个数都会改变一个方向。
如果1在2的左边,那么就是以向右(1)开始:
此后对于每个偶数的状态都是向左(0),对于每个奇数的状态都是向右(1)。
如果2在1的左边,那么就是以向左(0)开始:
此后对于每个偶数的状态都是向右(1),对于每个奇数的状态都是向左(1)。
设 \(\text{f[i][j][0/1]}\) 表示目前插入到了 i ,并且 i 在当前已经插入的不在前缀中的数的个数里面在第 j 位。1 表示下一位向右,0 表示下一位向左。
特别地,对于已经在确定前缀中的 i,j=0 ,如果 i-1 在前缀中,若 i-1 在 i 左侧(实际上并不会出现这种情况,我猜的),则 \(\text{f[i-1][0][1]--->f[i][0][0]}\);否则 \(\text{f[i-1][0][0]--->f[i][0][1]}\) 。如果 i-1 不在前缀中,则 i-1 一定在 i 右侧,那么 \(\text{f[i-1][j=1~i-p0][0]--->f[i][0][1]}\)
对于不在前缀中的 i ,枚举 j=(1~i-p0)。如果 i-1 在前缀中,则从 \(\text{f[i-1][0][1]}\)转移到 \(\text{f[i][j][0]}\);如果 i-1 不在前缀中:(1)枚举 k=1~j-1,从 \(\text{f[i-1][k][1]}\)转移到 \(\text{f[i][j][0]}\);(2)枚举 k=j~i-p0-1 ,从 \(\text{f[i-1][k][0]}\)转移到 \(\text{f[i][j][1]}\)
Code:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define ll long long
#define MAXN 55
ll f[MAXN][MAXN][2];
int n;
ll k;
int book[MAXN];
int q[MAXN];
const ll MX=(ll)(1e18);
inline ll r(int p)
{
int p0=p,psum=0;
for (int i=0;i<=n;i++)
for (int j=0;j<=n;j++)
f[i][j][0]=f[i][j][1]=0;
if (book[1]) f[1][0][0]=f[1][0][1]=1,psum++;
else f[1][1][0]=f[1][1][1]=1;//,printf("!????????");
for (int i=2;i<=n;i++)
{
if (book[i])
{
if (book[i-1])
{
if (q[i-1]<q[i])
{
f[i][0][0]+=f[i-1][0][1];
}
else
{
f[i][0][1]+=f[i-1][0][0];
}
}
else
{
for (int j=1;j<=i-psum;j++)
{
f[i][0][1]+=f[i-1][j][0];
}
}
if (f[i][0][0]>MX||f[i][0][1]>MX) return -1;
psum++;
}
else
{
for (int j=1;j<=i-psum;j++)
{
if (book[i-1])
{
f[i][j][0]+=f[i-1][0][1];
}
else
{
for (int k=1;k<=j-1;k++)
{
f[i][j][0]+=f[i-1][k][1];
if (f[i][j][0]>MX) return -1;
// if (p==1&&book[3]==1&&i==2) printf("!!!!!!!!!!%lld\n",f[1][1][1]);
}
for (int k=j;k<=i-psum-1;k++)
{
f[i][j][1]+=f[i-1][k][0];
if (f[i][j][1]>MX) return -1;
}
}
}
}
}
// if (p==1&&book[3]==1) printf("TEST:%lld\n",f[2][2][0]);
ll sum=0;
for (int j=0;j<=n-p0;j++) sum+=f[n][j][0]+f[n][j][1];
return sum;
}
int ans[MAXN];
ll sum=0;
inline void R()
{
for (int i=1;i<=n;i++)
{
/*if (i==3)
{
for (int x=1;x<=3;x++) printf("===%d\n",book[x]);
}*/
for (int j=1;j<=n;j++)
{
if (book[j]) continue;
book[j]=1;
q[j]=i;
ll rri=r(i);
// printf("sum=%lld rri=%lld\n",sum,rri);
if (sum+rri>=k||rri==-1)
{
break;
}
sum+=rri;
q[j]=0;
book[j]=0;
}
}
}
int main()
{
scanf("%d%lld",&n,&k);
R();
for (int i=1;i<=n;i++)
{
if (q[i]==0)
{
puts("-1");
return 0;
}
ans[q[i]]=i;
}
for (int i=1;i<=n;i++) printf("%d ",ans[i]); puts("");
return 0;
}
调试过程中出现的问题:总回转排列数可能会爆longlong,所以计算过程超出的时候要特判一下。

浙公网安备 33010602011771号