zoukankan      html  css  js  c++  java
  • 【[AHOI2009]同类分布】

    这是一篇有些赖皮的题解

    (如果不赖皮的话,bzoj上也是能卡过去的)

    首先由于我这个非常(sb)的方法复杂度高达(O(171^4)),所以面对极限的(1e18)的数据实在是卡死了

    但是这个时候可以骗一下

    一般来说肯定会有一个点的数据到达了(1e18),所以我们先将(1)(1e18)之间的答案算出来,这样再去算另一个左边界的话至少可以节省一半的常数,就算左边界不是很小也有可能还算点希望

    如果左边界特别小的话,可能就能幸运的卡过去

    这道题的左边界就非常小啊,我估计不超过(1e6)

    于是就卡过去了

    再来看看我这个非常(sb)的dp,我觉得可能没有人这么写

    我们设(dp[i][j][s][k])表示一个数填到了(i)位,最高位填的是(j),数位和是(s),且这些数中对于某一个数取模得(k)的数的个数

    至于这个某一个数是什么,我们当然是要最外面套上一个枚举数位和了

    那么答案很简单啊,如果我们当前枚举的数位和是(x)的话,答案肯定就跟(dp[][][x][0])有关系了

    那么这个方程怎么转移呢

    显然有

    [dp[i+1][p][j+p][(p*10^i+k)\%x]=sum_{t=0}^9dp[i][t][j][k] ]

    (t)表示上一位填的数,(i)是位数,(p)是这一位填的数,(j)是数位和,(k)是对当前枚举的数位和取模之后的值,(x)表示当前枚举的数位和

    同时我们发现好像直接去枚举(t)有些奢侈,我们可以直接把(sum_{t=0}^9dp[i][t][j][k])算好,于是我用(dp[i][10][j][t])来存下来(sum_{t=0}^9dp[i][t][j][k]),这样就可以优化转移了

    之后就是数位(dp)的套路卡上界了,大概就是注意一下卡上界的时候存一下前面的数位和

    复杂度大概是(O((log_{10}n*9)^4)),确实这是一个很垃圾的复杂度

    代码

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #define re register
    #define maxn 172
    #define LL long long
    #define max(a,b) ((a)>(b)?(a):(b))
    #define min(a,b) ((a)<(b)?(a):(b))
    LL dp[20][11][maxn][maxn];
    LL L,R;
    LL ans;
    int num[2],a[20][2];
    LL base[20];
    LL mod;
    inline LL qm(LL x) {return x>=mod?x-mod:x;}//优化一下取模
    inline void spilt(LL x,int pd)
    {
        num[pd]=0;
        while(x) a[++num[pd]][pd]=x%10,x/=10;
    }//分解数位
    inline void work(int x,int Len)
    {
        mod=x;
        memset(dp,0,sizeof(dp));
        for(re int i=0;i<=9;++i)
            dp[1][i][i][qm(i)]+=1,dp[1][10][i][qm(i)]+=1;
        for(re int i=1;i<Len;++i)//枚举长度
            for(re int j=0;j<=min(x,i*9);++j)//枚举数位和
                for(re int k=0;k<x;++k)//枚举对当前枚举的数位和x取模后的值
                    {
                        if(!dp[i][10][j][k]) continue;
                        for(re int p=0;p<=9;++p)
                            dp[i+1][p][j+p][(p*base[i]+k)%x]+=dp[i][10][j][k],dp[i+1][10][j+p][(p*base[i]+k)%x]+=dp[i][10][j][k];
                    }
    }
    inline LL slove(int pd,int x)
    {
        LL tot=0;
        for(re int i=1;i<num[pd];++i)
            tot+=dp[i][10][x][0]-dp[i][0][x][0];//统计所有位数小于给定数的,注意首位不能填0
        for(re int i=1;i<a[num[pd]][pd];++i)
            tot+=dp[num[pd]][i][x][0];//统计所有位数和给定数相同的,但是最高位小于给定数的
        LL now=a[num[pd]][pd],cnt=now;
        //now表示前面所有的数位和,cnt表示前面的数的值是多少
        //(比如说12345,卡到三这一位上,now=1+2=3,cnt=1*10+2*1=12)
        if(x-now<0) return tot;
        for(re int i=num[pd]-1;i;--i)//当前不同的那一位,[i+1,num]与x完全相同 
        {
            LL t=qm(x-cnt*base[i]%x);//根据算出后面的数位所需要的余数是多少
            for(re int j=0;j<a[i][pd];j++)
                tot+=dp[i][j][x-now][t];
            //当前第i位可以填的数必须要小于给定数当前的这一位,这里就按照dp的方式来统计答案
            now+=a[i][pd];
            cnt=cnt*10+a[i][pd];
            cnt=qm(cnt);
            if(x-now<0) break;
        }
        return tot;
    }
    int main()
    {
        scanf("%lld%lld",&L,&R);
        spilt(L,0),spilt(R+1,1);
        base[0]=1;
        for(re int i=1;i<=18;++i) base[i]=base[i-1]*10;
        if(R==1000000000000000000) 
        {
        	ans+=29410615796612778;
        	for(re int i=1;i<=num[0]*9;++i)
            	work(i,num[0]),ans-=slove(0,i);
            printf("%lld
    ",ans);
        	return 0;
    	}//去掉这个if在bzoj上也能卡过去
        for(re int i=1;i<=num[1]*9;++i)//枚举数位和
            work(i,num[1]),ans+=slove(1,i)-slove(0,i);
        printf("%lld
    ",ans);
        return 0;
    }
    
  • 相关阅读:
    java基础之冒泡排序
    java基础之HashSet如何保证对象的唯一性
    java基础之日期时间工具类
    java基础之抽象类和接口的区别
    java tomcat报错: Starting Tomcat v7.0 Server at localhost' has encountered a problem问题
    [bzoj 4196][NOI 2015]软件包管理器
    [bzoj 4034][HAOI 2015]树上操作
    [bzoj 1012][JSOI2008]最大数maxnumber
    详解Trie
    [bzoj 1047][HAOI2007]理想的正方形
  • 原文地址:https://www.cnblogs.com/asuldb/p/10206203.html
Copyright © 2011-2022 走看看