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;
}
posted @ 2022-04-11 21:30  wild_chicken  阅读(10)  评论(0)    收藏  举报