zoukankan      html  css  js  c++  java
  • [HAOI2010]计数 数位DP+组合数

    题面:

    你有一组非零数字(不一定唯一),你可以在其中插入任意个0,这样就可以产生无限个数。比如说给定{1,2},那么可以生成数字12,21,102,120,201,210,1002,1020,等等。

    现在给定一个数,问在这个数之前有多少个数。(注意这个数不会有前导0).

    样例输入:1020     样例输出:7

    题解:

    刚看到这道题的时候有点懵,,,,,

    其实仔细观察一下发现这题可以用组合做。

    注意到0的个数是不限的,

    而且如果位数小于给定n的话,肯定可以随便搭配,

    所以直接加上给定数的有效位+补全的0的全排列就好了,(全排列注意不能重复)

    有效位指的是不为0的位,

    这样虽然有前导0,但是可以看做是位数少的数,比如

    0012,0120分别可以看做12和120,

    这样不仅不用去掉前导0,还可以只用加一次,非常方便

    因此我们要求就只剩下位数相同的方案了,

    这就要用到数位DP了

    我们从前往后枚举,因为如果确定的当前位小于给定n的当前位的话,

    由于是从前往后枚举,只要当前位小了,后面的位不管怎么排列都是小于n的,

    因此我们加上去掉已经被确定的数后的全排列(全排列不能重复)就可以了,

    如果当前位相同,那方案数就要放在后面求(注意每确定一位都要在有的数集合中减去它)

    注意在第一位的时候不要枚举0就可以了

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 #define R register int
      4 #define AC 60
      5 
      6 #define LL long long
      7 LL len,ans,tot;
      8 int num[AC],have[AC],use[AC];
      9 /*第i位是什么,在哪里?
     10 设给定n的位数为len,那么位数小于len的显然可以随意组合,
     11 于是利用有效位组合计算。
     12 位数大于len的显然不合法
     13 所以只要计算位数等于len的即可,
     14 假设n的第i位不为0,那么如果用比第i位小的数摆在第i位上,
     15 这个数就肯定比n小了,所以后面就可以随意搭配了
     16 所以利用组合来算?
     17 之前只想到位数小于len的可以组合算,
     18 原来确定了前面的后面也可以组合算啊(废话),
     19 如果g[i]表示i位数,自由组合的合法方案,i>=有效位
     20 则g[i] = i! - (i-1)!
     21 看上去怎么这么玄啊。。。。
     22 果然是错的,,,因为如果有效位中有重复的话,这一部分就会被重复计算,
     23 比如26637,那么对于每一种合法排列,都会计算2次,因为6可以交换
     24 所以要去重,如果数i的出现次数为have[i],那么它会使得整个数列被多计算have[i]!次,
     25 同理,拓展到所有数,整个数列会被多计算have[i]! * have[i+1]! + .......
     26 因此设f[i]表示长度为i时的排列(无重复,有前导零)
     27 g[i]表示长度为i时的排列(无重复,无前导零)
     28 那么g[i] = f[i] - f[i-1];//相当于确定了第1位是0时,就必然有前导零了.
     29 设比当前位i大的数有k位,
     30 那么ans += k * newf[n-i];
     31 因为这时前几位已经确定,所以have和tot都变了,因此要用newf,而不是f
     32 因此可以发现,前面求的g和f到后面根本就没有用,所以还是不要求了,,,,,
     33 直接算就好了
     34 ans += tot!/have[1]! * have[2]!.....
     35 */
     36 void pre()
     37 {
     38     char c=getchar();
     39     while(c > '9' || c < '0') c=getchar();
     40     while(c >= '0' && c <= '9') 
     41     {
     42         num[++len]=c - '0',c=getchar();
     43         if(num[len] != 0) ++have[num[len]],++tot;//统计有效位
     44     }//读入
     45     have[0]=len - tot - 1;//获取0的个数
     46     if(!tot) 
     47     {
     48         printf("0
    ");
     49         exit(0);
     50     }
     51 }
     52 
     53 LL cal(int cnt)
     54 {
     55     LL tmp=0;
     56 /*    for(R i=1;i<=9;i++) tmp+=have[i],use[i]=have[i];
     57     use[0]=cnt - tmp;//获取0的个数,因为work中获取了,所以不用再次获取*/
     58     for(R i=0;i<=9;i++) use[i] = have[i];
     59     tmp=1;
     60     for(R i=2;i<=cnt;i++) 
     61     {
     62         tmp*=i;
     63         for(R j=0;j<=9;j++)
     64         {
     65             if(use[j] <= 1) continue;
     66             while(!(tmp % use[j]))
     67             {
     68                 tmp /= use[j];
     69                 --use[j];
     70                 if(use[j] == 1) break;
     71             }    
     72         }
     73     }
     74     return tmp;
     75 }
     76 
     77 void work()
     78 {
     79     LL tmp;
     80     have[0]++;//把之前为了计算小于len位的答案而减掉的0加回来
     81     for(R i=1;i<=len;i++)
     82     {
     83         tmp=0;
     84         for(R j=1;j<=9;j++) tmp+=have[j];
     85         have[0]=(len - i + 1) - tmp;//获取0的个数
     86         int b=(i == 1);//因为第一位不能用0代替
     87         for(R j=b;j<num[i];j++)//枚举放哪个,但只能尝试比当前位小的,且这样还可以保证不重复
     88         {
     89             if(!have[j]) continue;//没有就不能放
     90             --have[j];//先减掉
     91             /*if(i == 1)//error!!!但是只要第一个不是0的话,,,,后面的就不是前导零啊,,,有什么关系。。。。
     92             {//求g值,如果第一个是0的话,其他的have不会变,have[0]又是临时求的,所以没关系
     93                 ans += cal(len - i);
     94                 --have[0];
     95                 ans -= cal(len - i -1);
     96                 ++have[0];
     97             }*/
     98             ans+=cal(len - i);//只有第一位才不能有前导0,,,,
     99             ++have[j];
    100         }    
    101         if(num[i]) have[num[i]]--;//假设这一位也相同,那么就要减掉,0的时候不用减,因为0是临时算的(不过应该也可以不临时算?)
    102     }
    103     printf("%lld
    ",ans);
    104 }
    105 
    106 int main()
    107 {
    108 //    freopen("in.in","r",stdin);
    109     pre();
    110     if(len - 1 >= tot) ans+=cal(len-1);//这样会有前导零,但可以看做是位数不同
    111     work();
    112 //    fclose(stdin);
    113     return 0;
    114 }
  • 相关阅读:
    二叉搜索查找排序树
    多项式运算
    赫夫曼编码及应用
    利用python画出动态高优先权优先调度
    利用python画出SJF调度图
    支持向量机
    fisher线性分类器
    Codeforces Round #520 (Div. 2)
    Codeforces Round #510 (Div. 2)
    Codeforces Round #504 (rated, Div. 1 + Div. 2, based on VK Cup 2018 Final)
  • 原文地址:https://www.cnblogs.com/ww3113306/p/9125372.html
Copyright © 2011-2022 走看看