习题:Igor and Interesting Numbers(计数DP)
题目
思路
求第k大的题大多都是要一位一位的来求
我们考虑固定了首位有多少种情况
设\(tot_i\)表示数字\(i\)还能选多少次
\(dp_{i,j}\)表示前i个数一共占了j个位置
\(dp_{i,j}=\sum_{k=0}^{min(j,tot_i)}dp_{i-1,j-k}*C_j^k\)
方程的意义即为考虑第i个数加入的时候的情况
但是注意,这个dp求出来的方案一定是固定了有多少位的,并且不保证没有前导0
但是通过这个dp,我们只需要枚举首位是什么就可以得到没有前导0的情况,但是同样的,这里一样数位是固定的
我们可以通过先求出数位再一位一位试着填进去就可以得到答案
代码
#include<iostream>
#include<cstring>
using namespace std;
int n,t,_ind;
int tot[18],cho[18];
long long mem[205][205],f[205][205];
long long C(int n,int m)
{
if(mem[n][m])
return mem[n][m];
long long ret=1;
for(int i=n;i>=n-m+1;i--)
ret=ret*i/(n-i+1);
return mem[n][m]=ret;
}
long long dp1(int len)//带前导0,但是固定了位数
{
if(len==0)
return 1;
memset(f,0,sizeof(f));
f[0][0]=1;
for(int i=1;i<=16;i++)
{
for(int j=0;j<=len;j++)
for(int k=0;k<=tot[i]&&k<=j;k++)
f[i][j]+=f[i-1][j-k]*C(j,k);
}
return f[16][len];
}
long long dp2(int len)//不带前导0,同样要固定位数
{
long long ret=0;
for(int i=2;i<=16;i++)
{
if(tot[i])
{
tot[i]--;
ret+=dp1(len-1);
tot[i]++;
}
}
return ret;
}
int main()
{
cin>>n>>t;
for(int i=1;i<=16;i++)
tot[i]=t;
for(int i=1;i<=18;i++)
{
long long k=dp2(i);
if(k>=n)
{
_ind=i;
break;
}
n-=k;
}
for(int i=_ind;i>=1;i--)
{
for(int j=1;j<=16;j++)
{
if(i==_ind&&j==1)
continue;
if(tot[j]==0)
continue;
tot[j]--;
long long ret=dp1(i-1);
if(ret>=n)
{
cho[i]=j;
break;
}
tot[j]++;
n-=ret;
}
}
for(int i=_ind;i>=1;i--)
{
if(cho[i]<=10)
cout<<cho[i]-1;
else
cout<<(char)('a'+cho[i]-11);
}
return 0;
}

浙公网安备 33010602011771号