题目大意:有n块木板,长度分别为1,2,3,4.……,n,按照高低交错排列,即排列a1,a2,a3,......,an,任意i(1<i<n),满足a(i-1)<a(i)>a(i+1)或者a(i-1)>a(i)<a(i+1),将所有符合条件的排列按照字典序排列,求第c位的排列。
思路:
(1)设置两个数组w[][],m[][]。w[i][j]代表有i块木板,第一块的长度为j,第二块长度小于第一块长度的交错排列数目;m[i][j]代表有i块木板,第一块的长度为j,第二块长度大于第一块长度的交错排列数目。于是w[][]yum[][]满足递推关系:w[i][j]=∑m[i-1][k], k=1,2,3......,j-1;m[i][j]=∑w[i-1][k], k=j,j+1,......,i-1;
将第一个递推公式稍作转化,得:w[i][j] = ∑m[i-1][k] (k=1,2,3......,j-2) + m[i-1][j-1] = w[i][j-1] + m[i-1][j-1] ......①
根据将每块求补(即n-ai)后同样也是符合条件的排列,得:m[i][j]=w[i][n-j+1] ......②
①②两个式子就是最后得到的递推公式。
(2)然后就是根据得到的w[][]和m[][]一步步逼近了。另外逼近得到的数字并不是真实答案,只是表示相对于前一个答案的相对位置。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
int k,n,i,j,ans[21];
long long int c;
long long int w[21][21],m[21][21];
bool at_w,have_use[21];
memset(w,0,sizeof(w));
memset(m,0,sizeof(m));
w[1][1]=m[1][1]=1;
for (i=2;i<=20;i++)
for (j=1;j<=i;j++)
{
w[i][j]=w[i][j-1]+m[i-1][j-1];
m[i][i-j+1]=w[i][j];
}
scanf("%d",&k);
while (k--)
{
scanf("%d%I64d",&n,&c);
for (i=1,at_w=false;i<=20;i++)
{
if (c<=w[n][i]+m[n][i])
{
ans[1]=i;
if (c<=w[n][i])
at_w=true;
else
c-=w[n][i];
break;
}
else
c-=w[n][i]+m[n][i];
}
for (i=2;i<=n;i++)
{
if (at_w)
for (j=1;j<ans[i-1];j++)
if (c<=m[n-i+1][j])
{
ans[i]=j;
at_w=false;
break;
}
else
c-=m[n-i+1][j];
else
for (j=ans[i-1];j<=n-i+1;j++)
if (c<=w[n-i+1][j])
{
ans[i]=j;
at_w=true;
break;
}
else
c-=w[n-i+1][j];
}
memset(have_use,false,sizeof(have_use));
printf("%d",ans[1]);
have_use[ans[1]]=true;
for (i=2;i<=n;i++)
{
for (j=1;ans[i]>0;j++)
if (have_use[j]==false)
ans[i]--;
ans[i]=j-1;
have_use[ans[i]]=true;
printf(" %d",ans[i]);
}
printf("\n");
}
return 0;
}