数位DP:拆位分别进行DP,优化掉大范围
数位DP:
最重要一步:分情况讨论
板子:
点击查看代码
int dfs(int pos, int lim, ...) {
if(pos == 0) {
...
return ...
}
if((!lim) && dp[pos][...] != -1) return dp[pos][...];
int ans = 0;
int ed = lim ? bit[pos] : 9;
for(int i = 0; i <= ed; ++ i) {
ans += dfs(pos - 1, lim && i == ed, ...);
}
if(!lim) dp[pos][...] = ans;
return ans;
}
hud3709 Balanced Number

力距:e.g. 123,以2为支点,则1~2距离为1,权值为1,则为1,2~3距离(有向距离)为-1,则为-3,力矩则为1 + -3 = -2
数位DP将不能接受的数进行拆位优化

思路:枚举支点的all可能位置,其中对于10位数,假设已有后面9位答案,那么再加上10种状态加入即当前答案
使用记忆化搜索:
点击查看代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
ll dp[20][20][1500];
ll bit[20];
ll dfs(ll pos, ll o, ll sum, ll fl) {//o支点,sum力矩和,fl表当前为是否可for到9
if(pos == 0) {//最终sum=0即答案
if(sum == 0) return 1;
else return 0;
}
if(sum < 0) return 0;
if((!fl) && dp[pos][o][sum] != -1) return dp[pos][o][sum];//记忆化
//具体做法:
ll ans = 0;
ll ed = fl ? bit[pos] : 9;
for(ll i = 0; i <= ed; ++ i) {
//支点不改变,当可执行时将对应新位上的值加入
ans += dfs(pos - 1, o, sum + (pos - o) * i, fl && i == ed);
}
if(!fl) dp[pos][o][sum] = ans;
return ans;
}
ll ca(ll x) {
if(x < 0) return 0;
if(x == 0) return 1;
ll len = 0;
while(x > 0) {//拆分
bit[++ len] = x % 10;
x /= 10;
}
ll ans = 0;
for(ll i = len; i > 0; -- i) {//for长度:来确定位数
ans += dfs(len, i, 0, 1);
}
ans -= (len - 1);
return ans;
}
int main() {
memset(dp, -1, sizeof dp);
int t;
ll l, r;
scanf("%d", &t);
while(t -- ) {
scanf("%lld%lld", &l, & r);
printf("%ld\n", ca(r) - ca(l - 1));
}
return 0;
}
对于数最高位,显然若最高位i < 9则显然无法for到9
hdu3652
需要额外存上一位的状态,不断记录sum,判定%13是否整除

对每一位枚举pos可能,此时即sum可直接判断|13,记录flag,当结束时只需要看num是否为|13且flag = 1
对于区间答案,常用的是转换为前缀答案(相减)
计数问题
对于[a,b]中x出现的次数->转换为实现count(n,x):1~n中x出现的次数,以此类前缀和利用:[a,b] = count(b, x) - count(a - 1, x)
对于1~n中x出现次数 <=> 1出现在每一位的次数再相加
例如:
对于n = abcdedf,存在1 <= xxx1yyy <= n来说
分情况讨论:
当枚举x>0时:
- (1)xxx = 000 ~ abc-1: 则有yyy任取000~999,方案数=abc * 1000
- (2)xxx = abc,此时存在3种情况
- (2.1)d < x,因前三位固定,abc1yyy > abc0efg(不合法),方案数 = 0
- (2.2)d = x,(此时前4位达到上限)yyy可取000~efg,方案数 = efg + 1
- (2.3)d > x,此时yyy任取,1000种
当x = 0时:
对于条件(2)无影响,只影响(1)(其存在000这种前导零问题)
如:7位数字,当前3位000第4置入0,构成0000xxx(不合法,会将前导零都删掉)
所以对于x=0情况,(1)从001开始枚举
对符合情况((1)+(2)某一种)方案数进行累加,即可求出1在任意位上出现情况(同理扩展到x在任意位情况)
此时仍需考虑边界:
- 1在最高位: 则只考虑情况(2)即可
- 枚举0时: 若0出现在第4位,则前3位不能是000(不合法,不存在前导零),要至少从001开始
对于(1):xxxi(第i个)yyy的方案数 = xxx(固定) * 10^i,现实函数:
int get(vector<int> num, int l, int r)//前面位构成数字
{
int res = 0;
for (int i = l; i >= r; i -- ) res = res * 10 + num[i];
return res;
}
int power10(int x)//求10^i
{
int res = 1;
while (x -- ) res *= 10;
return res;
}
发现对于最高位:显然不能判断0~9,而是1~最高位当前数a(高位无),所以要特判断情况(1)不等于n-1
且又有:修正多计算出的前导零的个数
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
/*
001~abc-1, 999
abc
1. num[i] < x, 0
2. num[i] == x, 0~efg
3. num[i] > x, 0~999
*/
int get(vector<int> num, int l, int r)//前面位构成数字
{
int res = 0;
for (int i = l; i >= r; i -- ) res = res * 10 + num[i];
return res;
}
int power10(int x)//求10^i
{
int res = 1;
while (x -- ) res *= 10;
return res;
}
int count(int n, int x)//1~n中x出现次数
{
if (!n) return 0;
vector<int> num;
while (n)
{
num.push_back(n % 10);//取出每一位
n /= 10;
}
n = num.size();//n此时变为位数
int res = 0;//次数
for (int i = n - 1 - !x; i >= 0; i -- )//若最高位为0,则从次高位开始
{
//第一类:除最高位看情况(1)
if (i < n - 1)
{
res += get(num, n - 1, i + 1) * power10(i);
if (!x) res -= power10(i);//若为0,修正前导零导致的多算
}
//第二类:最高位非循环0~9,有限制0~值a,看情况(2)
if (num[i] == x) res += get(num, i - 1, 0) + 1;//(2.2)
else if (num[i] > x) res += power10(i);//(2.3)
}
return res;
}
int main()
{
int a, b;
while (cin >> a >> b , a)
{
if (a > b) swap(a, b);
for (int i = 0; i <= 9; i ++ )
cout << count(b, i) - count(a - 1, i) << ' ';
cout << endl;
}
return 0;
}
浙公网安备 33010602011771号