[CF747F] Igor and Interesting Numbers

前言

讲过没听懂,再讲没调出来的题——有价值的题,需要写题解的题

题目

洛谷

CF

讲解

配合代码食用更佳

这题用(DP)应该很好看出来吧(只是可能会想到奇怪的状压= =)

首先我们发现我们受到了数字长度的限制,不知道长度使我们的转移难上加难,所以我们先要把该数字的长度算出来

这是一个DP:(getlen函数)

我们令(dp[i][j])为只使用前(i)个数字,目前填了(j)个位置的方案数(当然是忽略了前导零的)

然后我们选择暴力枚举长度,计算出每种长度的方案数加上后是否达到了(k),如果达到了,那么说明该数的长度为(k),没达到就减去,为之后统计答案做准备

设枚举长度为(len)

由于忽略了前导零,所以(dp[0][i]=C(len-1,i))(习惯这么表达组合数= =)

对于其它的(dp[i][j]),我们考虑从(dp[i-1][j-k])转移

上一个状态是填了(j-k)个位置,剩下(len-(j-k))个位置,我们要填(k)个位置

那么(dp[i][j] = sum^{min {j,t}}_{k=0} dp[i-1][j-k]*C(len-(j-k),k))

然后我们就可以计算出该数字的长度了

这是另一个DP:(solve函数)

接下来我们要考虑在知道长度的情况下的第(k)小的数字是什么(因为我们已经把长度小于该数的数减掉了)

其实与上面的思路和DP都很类似

我们 从高位到低位从小到大 枚举当前位置的数字

然后用类似的方法统计个数,看是否达到(k),没达到就减去

由于我们枚举了当前位置的数字,所以在用DP统计方案数的时候0就可以作为首位计算

仍然是这个式子,只是(len)的含义不同了,是没选的位置的长度

(dp[i][j] = sum^{min {j,t}}_{k=0} dp[i-1][j-k]*C(len-(j-k),k))

当然我们还是需要特殊处理(i=0)的时候的DP值

因为(0)之前没有状态了,无法从前面转移

我们发现如果(j eq k),那么由于无法从前面转移,那么方案数为0

否则就是(C(len-j,k)),方案数依然是(C()总长(-)已填长度(=)剩余长度(,)选填长度())

所以两个DP只是有一个要不要管前导零的区别

代码

//12252024832524
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL;
const int MAXN = 165;
const int MAXJZ = 16;
int n,t,len;
int lim[MAXJZ];
char dic[MAXJZ];
LL C[MAXN][MAXN],dp[MAXN][MAXN];

int Read()
{
	int x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
void Put1(int x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
void Put(int x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x);
	if(c >= 0) putchar(c);
}
template <typename T>T Max(T x,T y){return x > y ? x : y;}
template <typename T>T Min(T x,T y){return x < y ? x : y;}
template <typename T>T Abs(T x){return x < 0 ? -x : x;}

void pre()
{
    C[0][0] = 1;
    for(int i = 1;i <= 160;++ i)//其实20左右就够用了
    {
        C[i][0] = 1;
        for(int j = 1;j <= i;++ j)
            C[i][j] = C[i-1][j] + C[i-1][j-1];
    }
    for(int i = 0;i <= 9;++ i) dic[i] = '0' + i;
    for(int i = 10;i < MAXJZ;++ i) dic[i] = 'a' + i - 10;
}
void getlen()
{
    for(len = 1;;++len)
    {
        memset(dp,0,sizeof(dp));
        for(int i = 0;i < Min(len,t+1);++ i) dp[0][i] = C[len-1][i];
        for(int i = 1;i < MAXJZ;++ i)
        {
            for(int j = 0;j <= len;++ j)//现在的总长度
            {
                for(int k = 0;k <= Min(j,t);++ k)//填k个i
                    dp[i][j] += dp[i-1][j-k] * C[len-(j-k)][k];
            }
        }
        if(dp[15][len] >= n) break;
        n -= dp[15][len];
    }
    //printf("len : %d
",len);
}
void solve()
{
    //printf("less : %d
",n);//935395
    LL dz;
    for(int i = 0;i < MAXJZ;++ i) lim[i] = t;
    for(int LEN = len;LEN >= 1;-- LEN)
    {
        int MIN = 0 + (LEN == len);//这一位是否能为0
        for(int i = MIN;i < MAXJZ;++ i)//枚举当前这一位
        {
            if(!lim[i]) continue;
            memset(dp,0,sizeof(dp));
            lim[i]--;
            //需要填的长度:LEN-1
            for(int j = 0;j < MAXJZ;++ j)//枚举现在填什么数字
                for(int lennow = 0;lennow <= LEN-1;++ lennow)//现在的长度
                    for(int k = 0;k <= Min(lennow,lim[j]);++ k)//填k个j
                    {
                        if(!j)
                        {
                            if(k == lennow) dz = 1;
                            else dz = 0;
                        }
                        else dz = dp[j-1][lennow-k];
                        dp[j][lennow] += dz * C[LEN-1-(lennow-k)][k];
                    }
            if(dp[15][LEN-1] >= n)
            {
                putchar(dic[i]);
                break;
            }
            n -= dp[15][LEN-1];
            lim[i]++;
        }
    }
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
    n = Read(); t = Read();
    pre();
    getlen();
    solve();
	return 0;
}
原文地址:https://www.cnblogs.com/PPLPPL/p/13392188.html