BUPT 2022 Spring Training #10
链接:https://vjudge.net/contest/487596
F - Carny Magician
题意
求所有长度为n的恰好有m位数值与下标一致的排列中字典序第k大的那个。\(0\leq m\leq n,1\leq n\leq 50,1\leq k\leq 10^{18}\)
思路
明明思路还挺明确的,但是写起来莫名各种难受。
我们采用从前往后一位一位确定的方法解题。
在每一位时,从小到大遍历这位的取值。从下一位到末尾的总排列数大于等于k时,该位的取值就确定了。再动态维护k,m就可以了。
那么现在只剩下一个问题,怎么求总排列数。
我们假设dp[k][i][j]表示长度为k的排列,其中有i和位置的\(P_i \notin [1,k]\),且恰好有j个位置的\(P_i=i\)的排列数。
显然稍加推导可以得到dp方程:
\[ dp[k][i][j]=\left\{
\begin{array}{lcl}
k!-\displaystyle \sum^{k-i}_{g=1}{C_{k-i}^{g}dp[k-g][i][0]} & & {j=0}\\
C_{k-i}^{j}dp[k-j][i][0] & & {0<j\leq k-i}\\
\end{array} \right. \]
于是这题就基本做完了。
还有非常恶心的卡精度问题。因为在n=50的情况下,dp比较大的项long long 根本开不下。注意到有\(1\leq k\leq 10^{18}\)的条件,那么我们先打个比较小的表看看dp在什么位置大于\(10^{18}\),比这个更大的统统置为\(2*10^{18}\).
代码
#include<cstdio>
#include<cstring>
#define int long long
const int N=60;
const int INF=1e18+1;
int n,m,t;
int count[N][N][N];
int c[N][N];
int totans[N];
bool vis[N];
void init_()
{
int k,i,j;
int jc;
memset(vis,false,sizeof(vis));
memset(c,0,sizeof(c));
c[0][0]=1;
for(k=1;k<=n;k++)
{
c[k][0]=1;
for(i=1;i<=k;i++)c[k][i]=c[k-1][i-1]+c[k-1][i];
}
memset(count,0,sizeof(count));
count[0][0][0]=1;
jc=1;
for(k=1;k<=n;k++)
{
jc*=k;
for(i=0;i<=k;i++)
{
if(k>20 || (k==20 && i>=3))count[k][i][0]=INF+10;
else
{
count[k][i][0]=jc;
for(j=1;j<=k-i;j++)count[k][i][0]-=c[k-i][j]*count[k-j][i][0];
}
for(j=1;j<=k;j++)
{
if(j>k-i)count[k][i][j]=0;
else
{
if(count[k-j][i][0]<=(INF+10)/c[k-i][j])
{
count[k][i][j]=c[k-i][j]*count[k-j][i][0];
}
else count[k][i][j]=INF+10;
}
}
}
}
return;
}
signed main()
{
int k,i,j;
int a,b;
int cnt;
scanf("%lld%lld%lld",&n,&m,&t);
init_();
/*for(k=1;k<=n;k++)
{
printf("* %lld %lld %lld\n",k,count[k][0][0],count[k][k][0]);
if(count[k][0][0]<=1e18)printf("1 ");
else printf("0 ");
if(count[k][k][0]<=1e18)printf("1\n");
else printf("0\n");
}*/
/*for(k=0;k<=20;k++)
{
printf("%lld %lld ",k,count[20][k][0]);
if(count[20][k][0]<INF)printf("0\n");
else printf("1\n");
}*/
for(k=1;k<=n;k++)
{
a=0;
for(i=1;i<=n;i++)
{
if(m==0 && i==k)continue;
if(vis[i])continue;
vis[i]=true;
cnt=0;
for(j=k+1;j<=n;j++)if(vis[j])cnt++;
if(i==k)b=a+count[n-k][cnt][m-1];
else b=a+count[n-k][cnt][m];
vis[i]=false;
if(b>=t)break;
a=b;
}
if(i>n)
{
printf("-1\n");
return 0;
}
totans[k]=i;
vis[i]=true;
t-=a;
if(i==k)m--;
}
for(k=1;k<=n;k++)printf("%lld ",totans[k]);
printf("\n");
return 0;
}

浙公网安备 33010602011771号