两篇讲的比较清楚的博客(感觉比官方题解讲的清楚些)
https://blog.csdn.net/The___Flash/article/details/105931836
https://blog.csdn.net/monochrome00/article/details/105921913/
思路:这类题最常规的思路是从第一位开始按位确定,假设当前在第i位,即前i-1个数已经确定的情况下求字典序第k‘小的数
那么我们就要知道第i位选择填j时的方案数是否会>=k'
所以需要预处理出dp[i][j][k]:i个数(不一定是排列数),有j个数在自己位置上,有k个数不可能找到自己的位置的方案数(想想为什么要设置状态k)
那么设第i位选择j后,前i位已经有x个数在自己位置上,后n-i个数中有y个数不可能找到自己位置对应的方案数是 dp[n-i][m-x][y],
如果>=k',那么第i位就可以确定是j
所以关键是要求这个dp[i][j][k],有两种方案:
第一种是直接去记忆化搜索(官方题解)
第二种是再设一个辅助状态:g[i][j]表示i个数,有j个元素不可能找到自己位置的方案数(博客题解)
#include<bits/stdc++.h> using namespace std; typedef double db; typedef long long ll; const int N=3e5+7; const ll inf=1e18+1e17; ll c[60][60]; ll f[60]; ll fac[60]; ll dp[60][60][60]; ll g[60][60]; ll n,m,k; bool vis[60]; int ans[60]; int main() { for(int i=0;i<=50;i++){ for(int j=0;j<=i;j++){ if(i==j||j==0) c[i][j]=1; else c[i][j]=c[i-1][j-1]+c[i-1][j]; } } f[0]=f[2]=1; for(int i=3;i<=50;i++){ if(i<=20) f[i]=f[i-1]*(i-1)+f[i-2]*(i-1); else f[i]=inf; } fac[0]=1; for(ll i=1;i<=50;i++){ if(i<=21) fac[i]=fac[i-1]*i; else fac[i]=inf; } for(int i=0;i<=50;i++){ for(int j=0;j<=i;j++){ if(j==0){g[i][j]=f[i];continue;} for(int k=0;k<=j&&k<=i-j;k++){ for(int l=max(0,-i+j+k+k);l<=k;l++){ ///挑k个出去,外面挑k个位置,外面位置对应的数中挑l个换进来,外面没被挑中的另外挑k-l个进来,进来的都可以全排列,外面位置全排列放通配符 if(inf/c[j][k]/c[i-j][k]/c[k][l]/c[i-j-k][k-l]/fac[j]/fac[k]<g[i-j-k][k-l]) g[i][j]=inf; else if(g[i][j]+c[j][k]*c[i-j][k]*c[k][l]*c[i-j-k][k-l]*fac[j]*fac[k]*g[i-j-k][k-l]>inf) g[i][j]=inf; else g[i][j]+=c[j][k]*c[i-j][k]*c[k][l]*c[i-j-k][k-l]*fac[j]*fac[k]*g[i-j-k][k-l]; } } } } for(int i=0;i<=50;i++){ for(int j=0;j<=i;j++){ for(int k=0;k<=i-j;k++){ if(inf/c[i-k][j]<g[i-j][k]) dp[i][j][k]=inf; else dp[i][j][k]=c[i-k][j]*g[i-j][k]; } } } scanf("%lld%lld%lld",&n,&m,&k); if(dp[n][m][0]<k){printf("-1 ");return 0;} /// k--; int fix=m,wn=0; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(vis[j]) continue; if(fix==0&&i==j) continue; int nwn=wn; int nfix=fix; if(i==j) nfix--; else if(!vis[i]) nwn++; if(j<i) nwn--; ///printf("i=%d j=%d wn=%d nwn=%d fix=%d nfix=%d k=%lld dp=%lld ",i,j,wn,nwn,fix,nfix,k,dp[n-i][nfix][nwn]); if(k>dp[n-i][nfix][nwn]) k-=dp[n-i][nfix][nwn]; else{ fix=nfix; ans[i]=j;vis[j]=true; wn=nwn; break; } } } for(int i=1;i<=n;i++) printf("%d%c",ans[i],i==n?' ':' '); printf(" "); }