数位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; }
时间: 2024-10-08 02:37:20