题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3709
题目大意:
求区间 \([x, y]\) 范围内“平衡数”的数量。
所谓平衡数是指:以某一位为支点,它左边的所有位的数乘以它到支点的距离之和等于它右边的所有位的数乘以它到支点的距离之和。
比如, \(4139\) 是平衡数,因为以 \(3\) 为支点,
- 左边的每一位数与距离的乘积和为 \(4 \times 2 + 1 \times 1 = 9\);
- 右边的每一位数与距离的乘积和为 \(9 \times 1 = 9\)
所以左右两边的数与距离的乘积和是相等的,所以 \(4139\) 是一个平衡数。
问题分析:
对于这道题,我们需要先枚举支点在哪一位,
然后,在确定支点所在的位的基础上,进行 数位DP 。
我们设状态 \(f[cid][pos][delta]\) 表示:
- 枚举的支点在第 \(cid\) 位,
- 当前所处的数位为 \(pos\),
- \(delta\) 为从最高位到第 \(pos + 1\) 位的每一位上的数字 \(num_i\) 与其与 \(col\) 位的距离的乘积和 \(\sum num_i \times (i-col)\)
然后我们开函数 dfs(int cid, int pos, int delta, bool limit)
,其中:
cid
、pos
、delta
的含义同上;limit
表示当前是否处于限制状态。
只要 pos<0
时 delta==0
则找到一种情况。
注意:因为枚举每一位 col
时,数字 \(0\) 的情况都会计算上去,所以需要在枚举每一位是都减去 \(0\) 的情况,最后再加上 \(0\) 的情况。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
long long f[20][20][1800], a[20];
void init() {
memset(f, -1, sizeof(f));
}
long long dfs(int cid, int pos, int delta, bool limit) {
if (pos < 0) return !delta;
if (!limit && f[cid][pos][delta] != -1) return f[cid][pos][delta];
int up = limit ? a[pos] : 9;
long long tmp = 0;
for (int i = 0; i <= up; i ++) {
int d = delta + (pos-cid)*i;
if (d < 0) continue;
tmp += dfs(cid, pos-1, d, limit && i==up);
}
if (!limit) f[cid][pos][delta] = tmp;
return tmp;
}
long long get_num(long long x) {
if (x < 0) return 0;
int pos = 0;
long long res = 0;
while (x) {
a[pos++] = x % 10;
x /= 10;
}
for (int cid = 0; cid < pos; cid ++) {
res += dfs(cid, pos-1, 0, true);
res -= 1; // 减去0的情况
}
return res + 1; // 加上0的情况
}
int T;
long long x, y;
int main() {
init();
scanf("%d", &T);
while (T --) {
scanf("%lld%lld", &x, &y);
printf("%lld\n", get_num(y) - get_num(x-1));
}
return 0;
}