|
|
## $HDU-4734-F(x)$
|
|
|
|
|
|
[题目传送门](http://acm.hdu.edu.cn/showproblem.php?pid=4734)
|
|
|
|
|
|
### 一、题目描述
|
|
|
|
|
|
题目给了个$F(x)$的定义:$$\large F(x) = A_n * 2^{n-1} + A_{n-1} * 2^{n-2} + ... + A_2 * 2^1 + A_1 * 2^0$$,$A_i$是十进制数位,然后给出$a、b,$求区间$[0,b]$内满足$f(i)<=f(a)$的$i$的个数。其中 $0 <= A,B < 10^9$
|
|
|
|
|
|
### 二、计算题目中的$F$函数
|
|
|
|
|
|
```c++
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
|
using namespace std;
|
|
|
/**
|
|
|
12345-->f(12345)=1*2^4+2*2^3+3*2^2+4*2^1+5*2^0
|
|
|
f(1234) =1*2^3+2*2^2+3*2^1+4*2^0
|
|
|
2*f(1234) =1*2^4+2*2^3+3*2^2+4*2^1
|
|
|
所以形成递归表示式
|
|
|
*/
|
|
|
//利用递归计算出F(x)
|
|
|
int f(int x) {
|
|
|
if (x == 0) return 0;
|
|
|
return f(x / 10) * 2 + x % 10;
|
|
|
}
|
|
|
|
|
|
//利用循环进行计算
|
|
|
int f2(int x) {
|
|
|
int res = 0;
|
|
|
int i = 0;
|
|
|
while (x) {
|
|
|
res += x % 10 * (1 << i);
|
|
|
i++;
|
|
|
x /= 10;
|
|
|
}
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
int main() {
|
|
|
cout << f(12345) << endl;
|
|
|
cout << f2(12345) << endl;
|
|
|
//极大值是1e9-1=99999999
|
|
|
cout << f2(1e9 - 1) << endl;
|
|
|
//输出4599
|
|
|
return 0;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 三、暴力怎么做?
|
|
|
用例解析:
|
|
|
以 $5$ $100$ 为例:即$A=5,B=100$
|
|
|
$F(A)=5*1=5$
|
|
|
也就是让我们求一下,在$F(0)$~$F(100)$之间,有多少个$F(x)<=5$
|
|
|
|
|
|
撸起袖子就是干!
|
|
|
|
|
|
```c++
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
|
using namespace std;
|
|
|
int F(int x) {
|
|
|
if (x == 0) return 0;
|
|
|
return F(x / 10) * 2 + x % 10;
|
|
|
}
|
|
|
int fa;
|
|
|
int main() {
|
|
|
int T;
|
|
|
int num = 1;
|
|
|
cin >> T;
|
|
|
|
|
|
while (T--) {
|
|
|
int x, y;
|
|
|
cin >> x >> y;
|
|
|
fa = F(x);
|
|
|
int cnt = 0;
|
|
|
for (int i = 0; i <= y; i++)
|
|
|
if (F(i) <= fa) cnt++;
|
|
|
|
|
|
printf("Case #%d: %d\n", num++, cnt);
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
```
|
|
|
毫无疑问,如此暴力,只能是$TLE$,看了一下时间限制,要求$500ms$,应该是一个数位$DP$问题。
|
|
|
|
|
|
### 四、正常的数位$DP$思路
|
|
|
|
|
|
#### 1、思考暴力$dfs$怎么做:
|
|
|
`int dfs(int pos,int sum,bool limit)`
|
|
|
|
|
|
* $pos$:我在哪里
|
|
|
* $sum$:我这个分身,走到现在,按$F(x)$的计算办法,已经拼接出的加权前缀和是多少,之所以要携带这个,是因为走完全程时需要判断总的$sum$是不是小于$F(A)$
|
|
|
|
|
|
* $limit$:是不是贴上界,之所以需要知道这个,是因为每位都需要上一位告诉自己,自己及以后各位是不是能取满,还是只能取一部分
|
|
|
|
|
|
**返回值**:$int$类型的$dfs$,思路就是 **向后思考** :如果我站在$pos$位置,并且前面的人告诉了我:以前有多少$sum$前缀和,是不是贴上界,
|
|
|
然后,我的任务就是计算,在给定的条件下,后续的枚举所有可能数字中,有多少符合条件的数字。
|
|
|
|
|
|
根据这个返回值定义,所以整体的结果就是:$dfs(al,0,true)$
|
|
|
|
|
|
#### 2、记忆化
|
|
|
根据我们的经验,$500ms$直接暴力$dfs$应该也不可能过的了,必须考虑是不是**存在重复计算**,能不能记忆化。简单想一想,肯定是可以记忆化的:到达哪种状态后,我不管你前面怎么到达我这里,一旦我的状态确定,那么我的后续所有可能中,符合条件的数字个数是一定的,我计算一次,想办法记下来,下回就可以重复利用。
|
|
|
|
|
|
那么按什么来记忆呢?根据经验,$limit$一般不记,只记忆$!limit$的状态结果,所以$dp[pos][sum]$的状态记忆数组定义就出来了。
|
|
|
|
|
|
代码也就很简单了:
|
|
|
|
|
|
```c++
|
|
|
#include <cstdio>
|
|
|
#include <cstring>
|
|
|
#include <iostream>
|
|
|
#include <cmath>
|
|
|
|
|
|
using namespace std;
|
|
|
const int N = 32;
|
|
|
const int M = 1024 * 10 + 10;
|
|
|
int f[N][M];
|
|
|
int a[N];
|
|
|
int fa;
|
|
|
int F(int x) {
|
|
|
if (x == 0) return 0;
|
|
|
return F(x / 10) * 2 + x % 10;
|
|
|
}
|
|
|
|
|
|
int dfs(int pos, int sum, bool limit) {
|
|
|
if (pos == 0) return sum <= fa;
|
|
|
if (sum > fa) return 0;
|
|
|
if (!limit && ~f[pos][sum]) return f[pos][sum];
|
|
|
int ans = 0;
|
|
|
int up = limit ? a[pos] : 9;
|
|
|
for (int i = 0; i <= up; i++)
|
|
|
ans += dfs(pos - 1, sum + i * (1 << (pos - 1)), limit && i == a[pos]);
|
|
|
|
|
|
if (!limit) f[pos][sum] = ans;
|
|
|
return ans;
|
|
|
}
|
|
|
|
|
|
inline int calc(int x) {
|
|
|
memset(f, -1, sizeof f);
|
|
|
int al = 0;
|
|
|
while (x) a[++al] = x % 10, x /= 10;
|
|
|
return dfs(al, 0, true);
|
|
|
}
|
|
|
|
|
|
int main() {
|
|
|
ios::sync_with_stdio(false);
|
|
|
cin.tie(0);
|
|
|
|
|
|
int T;
|
|
|
int cnt = 1;
|
|
|
cin >> T;
|
|
|
|
|
|
while (T--) {
|
|
|
int x, y;
|
|
|
cin >> x >> y;
|
|
|
fa = F(x);
|
|
|
printf("Case #%d: %d\n", cnt++, calc(y));
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
但很不幸运,<font color='red' size=4><b>Time Limit Exceeded</b></font>
|
|
|
|
|
|
|
|
|
### 五、释放减法的力量
|
|
|
|
|
|
[大神原文](https://www.cnblogs.com/sparkyen/p/11347119.html)
|
|
|
|
|
|
<font color='red' size=4><b>某大神写到:</b></font>刚开始一眼看到这题,算了一下 $F(999999999) = 4599$,也不是很大,感觉挺简单的,想着$dp[pos][sum]$不就完事了:$sum$记录从$len$(**最高位**)到$pos$位上累计的符合条件数字个数;
|
|
|
|
|
|
写出来交了一发,$TLE$,感觉就有点奇怪,翻了翻网上的题解,说是不能是$dp[pos][sum]$,要是$dp[pos][F(A) - sum]$才行;
|
|
|
|
|
|
也就是说,新定义$dp[pos][comp]$,$comp$代表剩下的从第$pos$位到第$1$位,累加起来的权重,不能超过$comp$;
|
|
|
|
|
|
**这是为什么呢?原因其实很简单:**
|
|
|
|
|
|
我们定义$dp[pos][sum]$的话,显然我们这 <font color='red' size=4><b> $T$组数据</b></font>……
|
|
|
|
|
|
每组数据只要$B$有变化,由于$sum$是从$B$最高位$(al)$**往下**累加权重,它跟$B$有密切关系($B$的长度$al$即为最高位),那么$dp$数组就必须要重新$memset(dp,-1,sizeof(dp))$,这样才不会出错;
|
|
|
|
|
|
那么我们如果是$dp[pos][comp]$呢,$comp$代表从第$1$位到第$pos$位最多还能累加起多少权重,那么它就和$B$没有关系,我们就不需要在输入每组数据后都重新将$dp$数组全部重置为$-1$。
|
|
|
|
|
|
```c++
|
|
|
#include <cstdio>
|
|
|
#include <cstring>
|
|
|
#include <iostream>
|
|
|
#include <cmath>
|
|
|
|
|
|
using namespace std;
|
|
|
const int N = 32; //数位的长度上限
|
|
|
const int M = 1024 * 10 + 10; //剩余的数大小(状态集合按这个划分)
|
|
|
int f[N][M]; //结果DP数组
|
|
|
int a[N]; //数位拆分出来的数组
|
|
|
int fa;
|
|
|
//利用递归计算出
|
|
|
int F(int x) {
|
|
|
if (x == 0) return 0;
|
|
|
return F(x / 10) * 2 + x % 10;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @param pos 数位位置
|
|
|
* @param st 剩余对比值,初始值是F(x)
|
|
|
* @param limit 是不是贴上界
|
|
|
* @return
|
|
|
*/
|
|
|
int dfs(int pos, int st, bool limit) {
|
|
|
if (pos == 0) return st >= 0; //如果到达最后,并且有剩余,表示f(i)<= f(x),计数++
|
|
|
if (st < 0) return 0; //中途或最后一旦发生小于0情况,剪枝
|
|
|
if (!limit && ~f[pos][st]) return f[pos][st];
|
|
|
int ans = 0;
|
|
|
int up = limit ? a[pos] : 9;
|
|
|
for (int i = 0; i <= up; i++)
|
|
|
//st = st - i * 2^ ( p-1 )
|
|
|
ans += dfs(pos - 1, st - i * (1 << (pos - 1)), limit && i == a[pos]);
|
|
|
|
|
|
if (!limit) f[pos][st] = ans;
|
|
|
return ans;
|
|
|
}
|
|
|
|
|
|
inline int calc(int x) {
|
|
|
int al = 0;
|
|
|
while (x) a[++al] = x % 10, x /= 10;
|
|
|
return dfs(al, fa, true);
|
|
|
}
|
|
|
|
|
|
int main() {
|
|
|
//不加78MS,加了31MS,效果还是很明显的
|
|
|
ios::sync_with_stdio(false);
|
|
|
cin.tie(0);
|
|
|
|
|
|
int T;
|
|
|
int cnt = 1;
|
|
|
cin >> T; //这个数值太BT了,最大10000次!
|
|
|
|
|
|
//优化的写法
|
|
|
memset(f, -1, sizeof f);
|
|
|
while (T--) {
|
|
|
int x, y;
|
|
|
cin >> x >> y;
|
|
|
fa = F(x); //计算出F(x)值,这是一个固定值
|
|
|
printf("Case #%d: %d\n", cnt++, calc(y));
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
|
|
|
|