HDU3709 Balanced Number —— 数位DP
题目链接:https://vjudge.net/problem/HDU-3709
Balanced Number
Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65535/65535 K (Java/Others)
Total Submission(s): 6615 Accepted Submission(s): 3174
to calculate the number of balanced numbers in a given range [x, y].
题解:
1.对于一个数,我们枚举每个位置作为平衡点,去判断是否存在一个位置使得这个数左右平衡。对于非0的数,如果它是平衡数,那么它的平衡点只有一个。(因为假设aaaa0bbb这个数的平衡点0,如果把平衡点往左移,那么左边变轻,右边变重,原本是平衡的,那改变之后就往右倾斜了。同理平衡点往右移。)所以是+1,正好一一对应。但是对于0,它的任意一个位置都可以作为平衡点,所以需要去重。
2. dp[pos][pivot][num]:处理到pos位置,以pivot为平衡点,且左力矩比右力矩大num的情况下,有多少种情况。
问:
1.为什么dp数组要有“pivot”这一维,直接:dp[pos][num]不行吗?
答:虽然有num记录了当前位的“左力矩 - 右力矩”的大小,但是如果两种情况的pivot不同,那么在pos后面的位置,他们的值的变化是不一样的,即这两种情况不是等价的。只有在相同的位置pos下,他们的平衡点pivot相同,且“左力矩 - 右力矩”的大小且相同,后面位置的数字变化才是等价的。
2.dp[pos][leftnum][rightnum] 这样记录不行吗?
答:其实这样跟dp[pos][num]的情况是一样的,都没有记录平衡点pivot的位置,导致后面位置的数值变化可能不等价。
代码如下:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cstdlib> 5 #include <string> 6 #include <vector> 7 #include <map> 8 #include <set> 9 #include <queue> 10 #include <sstream> 11 #include <algorithm> 12 using namespace std; 13 typedef long long LL; 14 const double eps = 1e-6; 15 const int INF = 2e9; 16 const LL LNF = 9e18; 17 const int MOD = 1e9+7; 18 const int MAXN = 1e6+10; 19 20 int digit[20]; 21 LL dp[20][20][4000]; 22 23 LL dfs(int pos, int pivot, int num, bool lim) 24 { 25 if(!pos) return (num==0); 26 //剪枝,因为先加左边的,后减左边的,最终结果需要为0,如果过程中都出现负数,那减下去更加负,所以不可能符合条件 27 if(num<0) return 0; 28 if(!lim && dp[pos][pivot][num]!=-1) return dp[pos][pivot][num]; 29 30 LL ret = 0; 31 int maxx = lim?digit[pos]:9; 32 for(int i = 0; i<=maxx; i++) //num+(pos-pivot)*i: 高位的加, 低位的减。这条式子很漂亮,刚好能满足力矩的计算 33 ret += dfs(pos-1, pivot, num+(pos-pivot)*i, lim&&(i==maxx)); 34 35 if(!lim) dp[pos][pivot][num] = ret; 36 return ret; 37 } 38 39 LL solve(LL n) 40 { 41 int len = 0; 42 while(n) 43 { 44 digit[++len] = n%10; 45 n /= 10; 46 } 47 48 LL ret = 0; 49 for(int i = 1; i<=len; i++) 50 ret += dfs(len, i, 0, true); 51 52 /* 53 为什么要减去len-1呢?原因是“0”这种情况,0在这里的表示是:000……00(len个0)。对于一个数 54 我们枚举每个位置作为平衡点,去判断是否存在一个位置使得这个数左右平衡。对于非0的数,如果 55 它是平衡数,那么它的平衡点只有一个,所以是+1,正好一一对应. 但对于000……00来说,它的每个 56 位置都可以作为平衡点( 因为左右两边都等于0),所以是+len,但是000……00却只是一个数,所以 57 需要减去重复计算的部分,即len-1. 58 */ 59 return ret-(len-1); 60 } 61 62 int main() 63 { 64 LL T, n, m; 65 scanf("%lld", &T); 66 memset(dp,-1,sizeof(dp)); 67 while(T--) 68 { 69 scanf("%lld%lld",&m,&n); 70 LL ans = solve(n) - solve(m-1); 71 printf("%lld\n", ans); 72 } 73 return 0; 74 }