855E

数位DP

昨天的B题,excited

又学习了一下数位dp...

数位dp要考虑几个比较重要的东西:1.前导0,2.天际线,3.记忆化的条件,4.细节

经常数位dp会问我们l->r区间中满足某某条件的数的个数,这个条件很明显满足可减性,所以一般转化为1->r的数-1->l-1的数,采用记忆化搜索的方式统计

这道题状态为dp[base][bit][state][pre],表示当前底数为base,统计的数有bit位,每种数出现次数用state表示,有没有前导0

然后考虑状态的改变,也就是前导0,天际线的变化,先考虑前导0的变化,如果当前放置的数不是0,那么前导0不存在,可以把这个数放进状态内,否则如果一直是前导0则不把前导0放入状态,前导0也要设进dp状态内;然后考虑天际线,我们先想一想所设的dp状态,dp[base][bit][state][pre],这是表示当前base下,这个数包括前导0,状态为state,有没有前导0,注意这个数是包括所有位数为bit的数,如果我们在记忆化搜索的时候碰到了天际线,也就是limit=1,那么我们不能对搜出的结果记忆化,也不能返回之前已经记忆化好的答案,因为天际线去除掉了一些数,而dp统计了所有长度为bit的数。

这样能够保证所有的数都被统计进去吗?是可以的,我们统计了天际线及以下长度为bit的数,但是那些长度小于bit的呢?其实我们把这些数转换为了加上前导零的数,这样就能很好地统计了。

所以数位dp我们要考虑1.前导0的变化,2.是否卡在天际线,3.dp状态不同情况下的变化,4.记忆化的条件,5.每次枚举第bit位是什么的范围,然后通过记忆化搜索实现。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int q;
ll dp[11][70][2048][2], f[20];
ll dfs(int base, int bit, int S, int pre, int limit) //flag = 2 超了 flag = 1正好 flag = 0 小了 
{
    if(bit == 0) return !S;
    if(!limit && dp[base][bit][S][pre] != -1) return dp[base][bit][S][pre];
    ll ret = 0;
    int mx = limit ? f[bit] : base - 1;
    for(int i = 0; i <= mx; ++i) 
    {
        if(i == 0 && pre) ret += dfs(base, bit - 1, S, pre, limit && (i == mx));
        else ret += dfs(base, bit - 1, S ^ (1 << i), 0, limit && (i == mx));
    }
    if(!limit) dp[base][bit][S][pre] = ret;
    return ret;
}
ll solve(int base, ll lim)
{
    f[0] = 0;
    while(lim) 
    {
        f[++f[0]] = lim % base;
        lim /= base;
    }
    return dfs(base, f[0], 0, 1, 1);
}
int main()
{
    memset(dp, -1, sizeof(dp));
    scanf("%d", &q);
    while(q--)
    {
        int base;
        ll l, r;
        scanf("%d%lld%lld", &base, &l, &r);
        printf("%lld\n", solve(base, r) - solve(base, l - 1));
    }
    return 0;
}
View Code

 

posted @ 2017-09-25 21:55  19992147  阅读(330)  评论(0编辑  收藏  举报