bzoj 1799: [Ahoi2009]self 同类分布 题解

【原题】

1799: [Ahoi2009]self 同类分布

Time Limit: 50 Sec  Memory Limit: 64 MB

Submit: 554  Solved: 194

[Submit][Status]

Description

给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数。

Input

Output

Sample Input

10 19

Sample Output

3

HINT

【约束条件】1 ≤ a ≤ b ≤ 10^18

Source

Day1

【分析】肯定是数位DP。不过望着50s的时限我大笑:这么宽?

于是匆匆整理了思路。

最多时18个9,也就是和最大值是162。首先我要先枚举和P。状态怎么表示呢?哦,f[i][j][k][sum]表示到第i位,首位是j,目前总和是sum,且目前模P的答案是K。推起来简单,就是统计的时候还是得一位一位的来,略麻烦。

但是写完后,我发现样例时过了,可连样例都跑得飞慢!!!

计算效率:162*18*10*162*162。咦?怎么算都是超时的!!!

【代码1】

#include<cstdio>
#include<cstring>
#include<cmath>
#define D 19
#define S 163
#define G 10
using namespace std;
typedef long long LL;
LL f[D][G][S][S],A,B,M[D][S];
LL PRE()
{
  int a[D]={0},cnta=0;LL ansa=0;
  for (;A;A/=10ll) a[++cnta]=A%10;
  int b[D]={0},cntb=0;LL ansb=0;
  for (;B;B/=10ll) b[++cntb]=B%10;
  for (int P=1;P<=162;P++)
  {
    memset(f,0,sizeof(f));
    for (int i=0;i<10;i++) f[1][i][i%P][i]=1ll;
    for (int i=1;i<cntb;i++)
      for (int j=0;j<=9;j++)
        for (int k=0;k<P;k++)
          for (int sum=0;sum<=P;sum++)
            if (f[i][j][k][sum])
            {
              for (int now=0;now<=9;now++)
                f[i+1][now][(k+now*M[i][P])%P][sum+now]+=f[i][j][k][sum];
            }
    LL ta=ansa,tb=ansb;
    int s=0,d=0,now=0;
    for (int i=cnta;i;i--)
    {
      for (int j=(i==1);j<a[i];j++) ansa+=f[i][j][now][P-d];
      s=(s+a[i])*10%P;d+=a[i];now=(P-s)*(P!=s);
    }
    s=0;d=0;now=0;
    for (int i=cntb;i;i--)
    {
      for (int j=(i==1);j<b[i];j++) ansb+=f[i][j][now][P-d];
      s=(s+b[i])*10%P;d+=b[i];now=(P-s)*(P!=s);
    }
  }
  if (!ansa) ansa--;
  return ansb-ansa;
}
int main()
{
  scanf("%lld%lld",&A,&B);A--;
  M[0][1]=0;for (int j=2;j<=162;j++) M[0][j]=1;
  for (int i=1;i<=18;i++)
    for (int j=1;j<=162;j++)
      M[i][j]=M[i-1][j]*10ll%j;
  printf("%lld",PRE());
  return 0;
}

于是无节操的去看题解。

有一位P党的大神是按位去枚举sum的,效率很高。

于是就。。。嘿嘿。。。赶紧借鉴一下。

然后发现空间上过不去。。。。

上面内存限制64M(当然BZOJ上超一点点没什么事),我68M!

于是开始压内存,把没用的全删除了——66M!

怎么办?打了一个很小的点(其实也不算是打点,只是把sum=162的这种情况去掉)

65M多!!还是卡不过去!!

气死了,就用数组自然溢出吧!咦?竟然过了!!

【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
#define D 19
#define S 160  //原来是163
using namespace std;
typedef long long LL;
LL f1[S-1][S][S-1],f2[S-1][S][S-1];LL A,B;
LL PRE(LL A)
{
  int a[D]={0},cnt=0,y,i,j,k,l,z,sum=0;LL ans=0,x=A,mul,now;
  if (!A) return 0;
  for (;A;A/=10ll) a[++cnt]=A%10,sum+=a[cnt];
  for (i=1;i<=cnt/2;i++) swap(a[i],a[cnt-i+1]);
  memset(f1,0,sizeof(f1));
  for (i=1;i<S;i++) f1[0][i][0]=1ll;
  if (x%sum==0) ans++;mul=1ll;
  for (i=0;i<cnt;i++)
  {
    x/=10ll;mul*=10ll;now=x*mul;sum-=a[cnt-i];
    for (z=0;z<a[cnt-i];z++)
    {
      for (j=sum+z+(sum+z==0);j<=sum+z+i*9;j++)
        y=(now%j)?j-now%j:0,ans+=f1[j-sum-z][j][y];
      now+=mul/10;
    }
    memcpy(f2,f1,sizeof(f1));
    memset(f1,0,sizeof(f1));
    for (j=0;j<=i*9;j++)
      for (k=j;k<S;k++)
        for (l=0;l<k;l++)
          for (z=0;z<=9;z++)
            f1[j+z][k][(l*10+z)%k]+=f2[j][k][l];
  }
  return ans;
}
int main()
{
  scanf("%lld%lld",&A,&B); 

  if (B>=999999999999999999ll) B=999999999999999998ll,printf("%lld",PRE(B)-PRE(A-1)+1);
  else printf("%lld",PRE(B)-PRE(A-1));
  return 0;
}

bzoj 1799: [Ahoi2009]self 同类分布 题解

时间: 2024-12-29 17:59:58

bzoj 1799: [Ahoi2009]self 同类分布 题解的相关文章

BZOJ 1799: [Ahoi2009]self 同类分布 ( 数位dp )

传送门   也不是很难,微坑的一点是dp数组在扫描每一次数位和的时候都要初始化, 因为没有存 用来mod的总数位和x 的位置... ... 然后这个dp的式子也稍微想了一下...还是练题太少... 1 #include<cmath> 2 #include<cstdio> 3 #include<cstring> 4 #include<iostream> 5 #include<algorithm> 6 #include<queue> 7

bzoj 1799: [Ahoi2009]self 同类分布

数位DP|记忆化搜索 听隔壁巨佬说DP和记搜可以互相转换 显然这个题是可以用记忆化搜索过的,那我们应传哪几个参数? 首先就是记搜最基本的位置标记. 然后就是枚举的数字各位之和,以及取模之后的余数(判断能否整除某个数). 最后就是判边界的参数. 当我们搜到最后一位时如果余数为0,并且各位之和=mod,那就直接返回1,否则返回零. 显然我们把这几个数装进数组里,是开不下的. long long 为8字节 \(8\times 20\times 200\times 200\times 200 \div1

【BZOJ】1799: [Ahoi2009]self 同类分布

[题意]给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数.1 ≤ a ≤ b ≤ 10^18 [算法]数位DP [题解] 感觉这种方法很暴力啊. 枚举数位和1~162(不能枚举0,不然会模0,相当于除0),记忆化f[pos][sum][val],sum表示当前数位和,val表示数字取模枚举的数位和. 每次sum+i和(val*10+i)%MOD转移. sum用减法优化,即记忆化(MOD-sum),但是枚举过程中都要memset,导致效率低下,记忆化效果很差. 要什么方法才能跑1.3s

bzoj 1799: [Ahoi2009]self 类似的分布 解读

[原标题] 1799: [Ahoi2009]self 同类分布 Time Limit: 50 Sec  Memory Limit: 64 MB Submit: 554  Solved: 194 [id=1799" style="color:blue; text-decoration:none">Submit][id=1799" style="color:blue; text-decoration:none">Status] Descr

bzoj1799 [Ahoi2009]self 同类分布

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1799 [题解] 一眼看过去,数位dp! 想了想,发现数字和最多也就是$m = 9 \times 18 = 162$种,好像不是很大. 考虑枚举每种数字和$p$,做一遍dp. 设$f_{i,j,k}$表示到第$i$位,当前真实数字模$p$余$j$,当前所有数字的和为$k$的方案数.(不考虑前导0问题) 这个可以通过一个$O(18\times p^2 \times 10)$的动态规划解决. 接

手打AC的第2道数位DP:BZOJ1799: [Ahoi2009]self 同类分布

先讲下个人对于数位DP的看法吧... 挺难理解的 首先需要明白的一点:前缀和很重要 其次:必须用到记忆化搜索(本人蒟蒻,必须要用这种方法降低难度) 然后呢,需要判断约束的条件:(1.前缀0(有时需要,有时不需要):2.数位的取值(基本都需要)) 比如这道题,乍一看无思路,然后呢.... 就会出现很神奇的事情 大胆尝试(第一次)3维(题解本来是4维的...) 居然就XJB A了... 略显蛋疼... 回归正题*2: 本题的状态有些难找,但是由于数位最多的也只有pos,所以就可枚举所有的数位和...

HYSBZ - 1799 self 同类分布

self 同类分布 HYSBZ - 1799 给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数.Sample Input 10 19 Sample Output 3 Hint [约束条件]1 ≤ a ≤ b ≤ 10^18 约束:一个数是它自己数位和的倍数,直接dp根本找不到状态,枚举数位和,因为总就162,然后问题就变成了一个数%mod=0,mod是枚举的,想想状态:dp[pos][sum][val],当前pos位上数位和是sum,val就是在算这个数%mod,(从高位算  *10

[AHOI2009]同类分布

[AHOI2009]同类分布 求区间\([l,r]\)内的数,满足自己能整除自己各个数位上的和的数的个数,\(l,r\leq 10^{18}\). 解 不难得知设\(f_n\)为n以内的满足条件的数,答案即\(f_r-f_l\),因为递推中要表现整除,可以考虑摸递推. 要表现各个数位的和,又要变现长度,而且还要表现摸数,还有余数,故设\(f[i][j][k][l]\)表示i位的数,各数位的和为j,摸数为k,余数为l的数的方案数,因此不难有 \[f[i][j][k][l]=\sum_{p=0}^9

BZOJ1799 self 同类分布 数位dp

BZOJ1799self 同类分布 题意 给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数. [约束条件]1 ≤ a ≤ b ≤ 10^18 题解 1.所有的位数之和<9*18=1622.所以,dp[i][j][k][m]表示有i位(允许有前导0),数位和为k,模数为m,前i位与模数的模为j的符合条件的数的个数.这样要炸空间,怎么办!!其实这个dp的最后一维可以省去,因为对于不同的m值,dp互不相干.这样还是要超时的,有5亿多.于是就要卡常数,具体见代码里面的枚举的上下界. 代码 #