zoukankan      html  css  js  c++  java
  • [BZOJ1833][ZJOI2010]数字计数(暴力+组合计数)

    Solution

    听说这题要用数位dp。
    不会。

    只能用暴力了......

    举个例子:求\([29,3246]\)中每个数码的出现次数。
    首先想到把每个数码分开求。
    好像很难。
    然后想到\([1,3246]\)的答案减去\([1,28]\)的答案。
    好像还是很难。
    最后突发奇想,把位数也分开,例如:求\([1,3246]\)中十位出现\(4\)的个数。
    好像很水。

    还是刚才那个例子,显然,从\(1\)开始的每\(100\)个数中,就有\(10\)个数的十位是\(4\)\(3246/100*10=320\),已经有\(320\)个十位是\(4\)的数了。那么剩下的\(46\)个数呢?显然,从\(1\)开始的每\(100\)个数中,十位是\(4\)的数都是第\(40\)个到第\(49\)个,\(3246\%100\)=\(46\),剩下\(46\)个数,第\(40\)个数到第\(46\)个数符合条件,也要算进去。

    然后会发现问题来了:\(0\)的个数好像多出来了?是的,例如计算\([1,3246]\)中十位出现\(0\)的个数,像刚才那样,每\(100\)个数中就有\(10\)个数的的十位是\(0\),是这\(100\)个数中的第\(1\)个数到第\(9\)个数,好像哪里不对?\([1,9]\)没有十位的0啊?

    可以看出,刚才计算某一位某个数码的出现次数,并没有管比这一位高的几位是几,也就是说可能全部都是\(0\)。实际上在刚才的方法中,将\([1,3246]\)全部看作了四位数:\(0001\),\(0002\),\(0003\)......

    那么,如何减去这些前导零呢?很简单,\([1,9]\)每个数有\(3\)个前导零,\([10,99]\)每个数有\(2\)个前导零,以此类推。

    计算部分长这样(不包括减去前导零):

    for(i=0;i<=cnt;i++)//枚举位数,位数的标号从0开始,便于用p[0]对应个位
    for(j=0;j<=9;j++)//枚举数码
    {
            num[j]+=x/p[i+1]*p[i];//num[j]记录j的出现次数
            mod=x%p[i+1];//p[i]为10的i-1次方
            if(mod<p[i]*j)continue;//x为区间右端点
            if(mod>p[i]*(j+1)-1)mod=p[i]*(j+1)-1;
            num[j]+=(mod-p[i]*j+1);
    }
    

    观察上面的代码,会发现问题又来了:计算\([1,3246]\)中十位为\(0\)的个数,和上面一样,剩下\(46\)个数,\(mod=46\)\(j=0\)\(mod-p[i]*j+1=47\),不是只有\(46\)个吗?

    进一步发现,这段代码中,每一位的\(0\)都会多算一个,可以看成:把\(0\)这个数以及它的前导零都多算了一次,也就是求了\([0,3246]\)每一位\(0\)的出现次数,那么应减去\([0,3246]\)的前导零。

    那么问题又来了,\([0,28]\)中算了\(0\)\([0,3246]\)中也算了\(0\),相减不就抵消了吗?为什么还要各自减去呢?

    这个问题将在以下代码中讲解:

    Code

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    using namespace std;
    long long p[200],a,b,ans[200],x,mod,y,t,num[200],i,j,cnt,tot;
    int main()
    {
        p[0]=1;
        cin>>a>>b;
        x=b;
    	//先处理[1,b]每个数码1~9的出现次数,[0,b]0的出现次数
        while(b)
        {
            cnt++;
            b/=10;
        }
        cnt--;
    	//p[0]对应个位,p[0]~p[cnt]对应b的每一位,总共cnt+1位 
        for(i=1;i<=cnt+1;i++)
        p[i]=p[i-1]*10;//p[i]表示10的i次方 
        for(i=0;i<=cnt;i++)//第i位 
        for(j=0;j<=9;j++)//数字j,出现几次 
        {
                ans[j]+=x/p[i+1]*p[i];
    			//每p[i+1]个连续整数中,这一位必会出现p[i]次j 
                mod=x%p[i+1];//个数不足p[i+1]的段,可能还出现j 
                if(mod<p[i]*j)continue;
    			//长度不足p[i]*j说明剩下的段中不存在第i位为j的
    			//这句不懂的用这段代码模拟一下[1,3426]中十位为4的个数的计算过程
                if(mod>p[i]*(j+1)-1)mod=p[i]*(j+1)-1;
    			//把剩余的段中这一位没有j的去掉
    			//这句不懂的用这段代码模拟一下[1,3466]中十位为4的个数的计算过程 
                ans[j]+=mod-p[i]*j+1;//[p[i]*j,mod]的数中,这一位都为j
        }
        for(i=0;i<cnt;i++)//减去前导零
        {
            t=p[i]; 
            if(i==0)t=0;//位数为i+1的最小非负整数为t,因为要减掉0,所以是非负 
            y=p[i+1]-1;//位数为i+1的最大整数为y 
            if(y>x)y=x;//y不能超过x,即不能超过b 
            ans[0]-=(cnt-i)*(y-t+1);//把所有数都按b的位数来看,减去前导零的个数 
    		//[t,y]的每个数都会有cnt-i个前导零 
        }
        x=a-1;
    	//处理[1,a-1]数码1~9的出现次数,以及[0,a-1]0的出现次数
        tot=a-1;
        cnt=0;
        if(tot==0)cnt=1;
        while(tot)
        {
            cnt++;
            tot/=10;
        }
        cnt--;
        for(i=0;i<=cnt;i++)
        for(j=0;j<=9;j++)
    	{
            	num[j]+=x/p[i+1]*p[i];
           	 	mod=x%p[i+1];
            	if(mod<p[i]*j)continue;
            	if(mod>p[i]*(j+1)-1)mod=p[i]*(j+1)-1;
            	num[j]+=(mod-p[i]*j+1);
    	}
        /*
    	讲解刚才那个问题:
    	刚才说会把0给算进去,但是两个区间中都会算到0,相减是不是就能抵消了?
    	对于这个程序来说,如果a-1是一位数,那么cnt=0,下面的那个循环不会进入,
    	这个区间内的0不会被算;b不是一位数,上面那个区间的0会被算,就多出来了。
    	所以在处理两个区间时,各自减掉各自的0就行了。
    	*/ 
        if(cnt!=0)
        for(i=0;i<cnt;i++)
        {
            t=p[i];
            if(i==0)t=0;
            y=p[i+1]-1;
            if(y>x)y=x;
            num[0]-=(cnt-i)*(y-t+1);
        }
        for(i=0;i<=9;i++)//两个区间相减,得到[a,b]的结果 
        cout<<ans[i]-num[i]<<" ";
        return 0;
    }
    

    注意要开long long,不然\(30\)分,当然不用全开long long,只是因为我懒得去想哪些不用开。

    历经千辛万苦,终于利用暴力解出一道数位dp。

    于是,我至今不会数位dp。

  • 相关阅读:
    H3c实验室-(OSPF,Nat,STP,Dhcp,Acl)v.1)
    武科WUST-CTF2020“Tiki组 ”
    MRCTF 2020-“TiKi小组”
    mybatis-sqlite日期类型对应关系
    docker安装postgresql
    docker常用命令
    java sqlite docker,sqlite出错
    jenkins之SSH Publishers环境变量
    线程池(6)-submit与execute区别
    线程池(5)-停止线程池里的任务
  • 原文地址:https://www.cnblogs.com/cyf32768/p/12196297.html
Copyright © 2011-2022 走看看