题意:
给你T次询问,每次询问给你两个数l, r(0 <= l <= r <= 1e18),问你l,r间有多少个平衡数,平衡数定义:平衡的数字必须与某些数字的支点保持平衡。例如,4139是一个枢轴固定为3的平衡数。左侧部分和右侧部分的扭矩分别为4 * 2 + 1 * 1 = 9和9 * 1 = 9。
思路:
- dp[pos][sum], 一维表示一个数从左到右第pos位,二维sum表示支点左右相加后的平衡值,支点左边的数设为正,右边设为负,最后sum = 0则是平衡数(当sum < 0时显然就可以剪枝了)。
- 一个数有cot位,遍历每一位支点dp一次,把贡献相加,但要注意每换个支点dp,支点为0,支点左右全为0的情况就多算了一次,所以最后贡献和要减去cot。(如5-999,有三种情况0,00,000,都代表0,减去2)
code:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
long long dp[30][3005];
int head[30];
long long dfs(int pos, int bal, int sum, bool limit) { //pos表示数位,bal表示支点,sum表示平衡值
if(pos == -1) {
if(sum == 0) return 1;
else return 0;
}
if(sum < 0) return 0; //sum < 0已不可能是一个平衡数,剪枝
if(!limit && dp[pos][sum] != -1) return dp[pos][sum];
int max_digit = limit ? head[pos] : 9;
long long ans = 0;
for(int i = 0; i <= max_digit; i++){
ans += dfs(pos - 1, bal, sum + (pos - bal) * i, limit && i == max_digit); //pos - bal
}
if(!limit) dp[pos][sum] = ans;
return ans;
}
long long solve(long long l){
if(l < 0) return 0;
int cot = 0;
while(l){
head[cot++] = l % 10;
l /= 10;
}
long long ans = 0;
for(int i = 0; i <= cot - 1; i++){
memset(dp, -1, sizeof(dp));
ans += dfs(cot - 1, i, 0, true);
}
return ans - cot + 1; //减去全零的情况有几位有几次重复的,最终只需保留一种,
}
int main(){
int T;
long long l, r;
scanf("%d", &T);
while(T--){
scanf("%lld%lld", &l, &r);
printf("%lld\n", solve(r) - solve(l - 1));
}
}