bzoj1799: [Ahoi2009]self 同类分布

链接

  http://www.lydsy.com/JudgeOnline/problem.php?id=1799

题解

  这才是真正的题目啊(相对于前面两道)。
  不难发现,9*18=162,就是说数字之和是很小的,那么套用NOIP2016 day2 T1的套路,加一维表示模数。
  直接枚举模数p,然后统计1到N内有多少数各位之和为p且对p取模为0。
  即f[i][j][k]表示i位数,和为j,对p取模余k的数字有多少种。(无限制)
  g[i][j][k]同理,只不过加上了对数的大小的限制。
  最蛋疼的地方在于转移,这道题把我教训了一顿。
  一开始,直接用i-1的去转移i的,结果T的很惨。后来改成用i去转移i+1的,就AC了。
  为啥呢?
  举个栗子:假设N=20,枚举的p=10。那么在[1,20]内,各位数字之和为3的只有3、12,你的状态有f[1~2][0~3][0~9],实际有用的只有f[1][3][3]和f[2][3][2],其它的状态全都等于0,这样就造成了时间上的浪费,如果我们从开始去更新后面的,每次循环到一个值为0的就continue掉,能快很多。
  其实这样分析完了,发现写一个记忆化搜索是最好的选择。(但是我比较懒,就不写了)

代码

//数位DP
#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
ll N, f[20][200][200], num[20], g[20][200][200], mi[200], cnt;
void init(ll x)
{for(;x;x/=10)num[++N]=x%10;}
ll dp(ll p)
{
    ll i, j, k, now;
    memset(f,0,sizeof(f)), memset(g,0,sizeof(g));
    for(i=0;i<=9;i++)f[1][i][i%p]=1;
    for(i=0;i<=num[1];i++)g[1][i][i%p]=1;
    for(i=1;i<N;i++)
        for(j=0;j<=p;j++)
            for(k=0;k<p;k++)
            {
                if(f[i][j][k]==0 and g[i][j][k]==0)continue;
                for(now=0;now<=9;now++)
                    f[i+1][j+now][(k+now*mi[i])%p]+=f[i][j][k];
                for(now=0;now<=num[i+1];now++)
                    if(now!=num[i+1])g[i+1][j+now][(k+now*mi[i])%p]+=f[i][j][k];
                    else g[i+1][j+now][(k+now*mi[i])%p]+=g[i][j][k];
            }
    return g[N][p][0];
}
ll work(ll x)
{
    ll i, ans=0;
    N=0;
    init(x);
    for(i=1;i<=9*N;i++)ans+=dp(i);
    return ans;
}
int main()
{
    ll a, b;
    mi[0]=1;
    for(int i=1;i<=18;i++)mi[i]=mi[i-1]*10;
    scanf("%lld%lld",&a,&b);
    printf("%lld",work(b)-work(a-1));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值