题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3709
题目大意: 题目先给出平衡数的概念:数n以数n中的某个位为支点,每个位上的数权值为(数字xi*(posi - 支点的posi)),如果数n里有一个支点使得所有数权值之和为0那么她就是平衡数。比如4139,以3为支点,左边 = 4 * (4 - 2) + 1 * (3 - 2) = 9,右边 = 9 * (1 - 2) = -9,左边加右边为0,所以4139是平衡数。现在给出一个区间[l,r],问区间内平衡数有多少个?
题目大意: 题目先给出平衡数的概念:数n以数n中的某个位为支点,每个位上的数权值为(数字xi*(posi - 支点的posi)),如果数n里有一个支点使得所有数权值之和为0那么她就是平衡数。比如4139,以3为支点,左边 = 4 * (4 - 2) + 1 * (3 - 2) = 9,右边 = 9 * (1 - 2) = -9,左边加右边为0,所以4139是平衡数。现在给出一个区间[l,r],问区间内平衡数有多少个?
解题思路:第一道数位DP,之前一直不会做这类题目,今天花了两个小时看了几篇博客,略懂皮毛,也参照某神牛的代码敲了下这题。
这类题目用逆推要比正推好做,方法是记忆化搜索。每次向下传递目前的状态,下面每次都返回通过这些状态后面能得到的结果。
因为要权值之和为0,我们枚举每个支点o,然后从高位往地位搜索并记录状态,这里的状态为当前的位置pos,之前的权值之和pre、支点o,这三个组合起来就可以表示一个状态。每种状态都至多遍历一次,如果第二次遍历到某个状态,就直接返回前一次往下遍历的结果。
上一段已经解释了Dfs中的三个参数,那还剩下一个参数limit是干什么的?limit表示是否有上界,如果我们要找的是[0,12345,现在找到123,这时limit还是1,如果下一个枚举到的数是3,limit就变成0,以后都可以枚举到9而不是到5.
测试数据:
2
0 9
7604 24324
C艹代码:
#include <stdio.h>
#include <string.h>
#define MAX 2000
#define int64 long long
int64 dp[20][20][MAX]; //dp记忆化搜索用
int64 digit[20],left,right; //left和right为输入的一大一小值
int64 Dfs(int64 pos,int o,int64 pre,int limit) {
//pos表示当前位置,o表示支点,pre表示从最高位到pos的力矩之和,limit表示是否有上限 1有 0无
if (pos == -1) return pre == 0; //已经全部组合
if (pre < 0) return 0; //前面组合而成的力矩之和已经小于0,后面的也都是负数
if (!limit && dp[pos][o][pre] != -1)
return dp[pos][o][pre]; //没有上限且当前的状态之前已经搜索过
int64 sum = 0;
int64 end = limit ? digit[pos] : 9; //有上限就设为上限,否则最高到9
for (int i = 0; i <= end; ++i) {
int64 next = pre; //枚举下一个状态
next += (pos - o) * i;
sum += Dfs(pos-1,o,next,limit && i == end);
}
if (!limit) dp[pos][o][pre] = sum;
return sum;
}
int64 Cal(int64 x) {
int64 pos = 0,ans = 0; //位数,总个数
while (x) {
digit[pos] = x % 10;
x /= 10,pos++;
}
for (int o = 0; o < pos; ++o) //枚举支点
ans += Dfs(pos-1,o,0,1);
return ans - pos + 1; //去除全是0的情况
}
int main()
{
int i,j,k,t;
scanf("%d",&t);
while (t--) {
scanf("%lld%lld",&left,&right);
memset(dp,-1,sizeof(dp));
printf("%lld\n",Cal(right) - Cal(left-1));
}
}