习题: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;
}
posted @ 2020-07-20 10:25  loney_s  阅读(127)  评论(0)    收藏  举报