で\(1 \ SIM N \)完全な配列(\ \ {a_iを\} \ ) インターリーブのサイズのみiを満たす(すなわち、任意の位置、\(A_ {I-1} <a_iを> A_ {I + 1 I-ORA_。1 {}}> a_iを<A_。1} + I {\) )順列方式が正当であるc番目の有効な質問全体構成方式、\(N- \のLeq 20、C \ ^ {2のLeq 63} \) 。
ソリューション
まず、いくつかのプログラムの最初のを見つける、埋めるために自然な方法を使用しようとするが、その数は中に置くことができない位置に配置されているので問題は、再発の一般的な構成と異なっているが、自然の配置は、それは、注意を払っていることに注意サイズ、そしてそれゆえ、離散再帰的と考えられます。
最も自然な表現を埋めるために左に、だけでなく、シーケンスの長さを示しているものを、合法的な順序を守るためには、左端に望むことができる、記入しようとする\(a_iを> A_ {I + 1} \) 、実行する心の中でそうでない場合は、0を表しますセット\(F [I] [J] [K] \) I配列の長さを表し、Jの多数の最も左の原子、kはプログラムナンバーの状態であり、困難有します
\ [F [I] [J] [0] = \ sum_ {k = J} ^ {I-1} F [I-1] [K] [1] \]
\ [F [I] [J] [ 1] = \ sum_ {k = 1} ^ {J-1} F [I-1]を[K] [0] \]
ボーダー:\([1] [1] [1] [1] [1] F [0] = F = 1 \)、余りがある0
だから我々は、直接1における状態の最初の位置は、0、1は優先順位を埋めるためにできることに注意してください、大規模なテスト埋めるためにプログラムに小さな数を使用することができます。
参照コード:
#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
#define ll long long
using namespace std;
bool used[21];
ll dp[21][21][2];
il void prepare();
int main(){
int lsy;scanf("%d",&lsy),prepare();
while(lsy--){
int n,last;bool p;ll c;
memset(used,0,sizeof(used));
scanf("%d%lld",&n,&c);
for(int i(1);i<=n;++i){
if(dp[n][i][1]>=c){
last=i,p=1;
break;
}
else c-=dp[n][i][1];
if(dp[n][i][0]>=c){
last=i,p=0;
break;
}
else c-=dp[n][i][0];
}printf("%d ",last),used[last]|=true;
for(int i(2),j,k;i<=n;++i){
p^=true;
for(j=1,k=0;j<=n;++j){
if(used[j])continue;++k;
if((p&&j>last)||(!p&&j<last))
if(dp[n-i+1][k][p]>=c){
last=j,used[j]|=true;
break;
}
else c-=dp[n-i+1][k][p];
}printf("%d ",last);
}putchar('\n');
}
return 0;
}
il void prepare(){
dp[1][1][0]=dp[1][1][1]=1;
for(int i(2),j,k;i<=20;++i)
for(j=1;j<=i;++j){
for(k=j;k<i;++k)dp[i][j][0]+=dp[i-1][k][1];
for(k=1;k<j;++k)dp[i][j][1]+=dp[i-1][k][0];
}
}