• [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;
    }
    
  • 相关阅读:
    java实现动态上传多个文件并解决文件重名问题
    MySQL存储过程之事务管理
    Java IO--字符流--InputStreamReader 和 OutputStreamWriter
    Java IO--字符流--BufferedReader和BufferedWriter
    java线程同步小结
    进程和线程的区别与联系
    java中“==”和equals方法的区别,再加上特殊的String引用类型
    java.util.Date 与 java.sql.Date 之间的转换
    Java Applet 与Servlet之间的通信
    Applet 应用程序进行数字签名,对系统文件进行读写操作
  • 原文地址:https://www.cnblogs.com/PPLPPL/p/13392188.html
Copyright © 2020-2023  润新知