看到題解裏好像都是用$DP$解決的,本着禁止DP的原則,我來提供一發純數學其實和DP本質相同的題解,前兩天剛反演題,腦子炸了,原本說換換腦子,結果仍是數學
首先受進制思想啓發,咱們不妨按位考慮,考慮這一位選一對排列編號形成的影響——即讓整個數的編號向後推移了多少
容易想到,這一位選一,編號增長了以後幾位知足條件任選的方案數,即第$i$位選一,$cnt$表示前幾位選了幾個一
$$id+=\sum_{j=0}^{min(i-1,L-cnt)}calc(i-1,j)$$
$clac(x,y)$表示前面$y$位,選$x$位爲一的方案數,這個就是一個可重集排列問題,即
$$clac(x,y)=\frac{y!}{x!*(y-x)!}$$
由於$n!$太大會爆$long~long$,因此咱們能夠使用惟一素數分解定理把階乘拆成質因子的乘積,而後再乘起來
上代碼:
ios
#include<iostream> #include<cstdio> #include<cstring> #define int long long using namespace std; int pr[10]={2,3,5,7,11,13,17,19,23,29}; int n,k,rk,cnt,ans[50],cp[20]; void add(int x,int c) { //惟一素數分解 for(int i=1;i<=x;i++) for(int tmp=i,j=0;j<10&&tmp>1;j++) while(tmp%pr[j]==0) tmp/=pr[j],cp[j]+=c; } int make(int x,int y) { //可重集排列 int ret=1; memset(cp,0,sizeof(cp)); add(x,1),add(y,-1),add(x-y,-1); for(int i=0;i<10;i++) for(int j=1;j<=cp[i];j++) ret*=pr[i]; return ret; } signed main() { scanf("%lld%lld%lld",&n,&k,&rk); rk--; //由於有=0的狀況,因此rk-1 if(!rk) { for(int i=1;i<=n;i++) printf("0"); printf("\n"); return 0; } for(int i=n;i;i--) { //按位考慮選或不選 int sum=0; for(int j=0;j<=min(i-1,k-cnt);j++) sum+=make(i-1,max(j,i-1-j)); if(rk>=sum) rk-=sum,ans[i]=1,cnt++; } for(int i=n;i;i--) printf("%lld",ans[i]); printf("\n"); return 0; }