This commit is contained in:
HuangHai
2025-08-30 18:35:01 +08:00
commit 1f397eca87
6258 changed files with 1487241 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int pw[N];
typedef long long ll;
const int mod = 1e9 + 7;
int main() {
pw[0] = 1; // 10^0=1,递推的起点
// 1、一日取模,终生取模~
// 2、为了防止运算过程中溢出,所以在乘法时注意加上ll类型转换
// i ∈ [1,N-1] 共19位
for (int i = 1; i < N; i++) pw[i] = (ll)10 * pw[i - 1] % mod;
for (int i = 1; i < N; i++) printf("%d ", pw[i]);
/*
输出结果
10 100 1000 10000 100000 1000000 10000000 100000000 1000000000 999999937 999999307 999993007 999930007 999300007 993000007 930000007 300000007 49 490
*/
return 0;
}

View File

@@ -0,0 +1,58 @@
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
typedef long long ll;
const int N = 170;
int a[20], mod;
/*
f[pos][sum][pre]
第一维:数字的每一位,从18~1有效
第二维:已经走完的路径,每位的数位和
第三维:已经走完的路径,每位的拼接值(十进制)
前提:已经走到pos这个位置,前面积累下来的数位和是sum,前面的数字拼接pre(10进制), (是不是贴上界)
返回:在满足前提条件下,后续符合题意的数字有多少个?
*/
ll f[20][N][N];
//此处注意一下:pre如果是类型ll,则会第9个点TLE,是类型int,则可以AC!
ll dfs(int pos, int sum, int pre, bool limit) {
// 如果走完了全程,pre=原数%mod,如果最后pre还能整除mod,并且,sum等于mod,则贡献数字1个
if (pos == 0) return pre == 0 && sum == mod;
if (!limit && ~f[pos][sum][pre]) return f[pos][sum][pre];
ll ans = 0;
int up = limit ? a[pos] : 9;
for (int i = 0; i <= up; i++) {
// sum+i:数位和+i
// (pre * 10 + i) % mod : 不断的拼接前缀数字(十进制),但不能盲目记录原始数字,需要分组,按什么分组呢?当然是按枚举的当前mod分组
// 上面这句是本题的精华!
ans += dfs(pos - 1, sum + i, (pre * 10 + i) % mod, limit && i == a[pos]);
}
//记忆化结果
if (!limit) f[pos][sum][pre] = ans;
return ans;
}
ll calc(ll x) {
if (x == -1) return 0; //处理一下边界情况
int al = 0;
while (x) a[++al] = x % 10, x /= 10;
ll res = 0;
for (mod = 1; mod <= 9 * al; mod++) { // max(mod)=9*18=162,数量并不大,可以枚举
memset(f, -1, sizeof f); //每次取的模都会变化,这样状态数组就不再可以重用,需要每次清空
res += dfs(al, 0, 0, true); //其实本题的本质是按分组讨论的方式完成的数量的统计,然后再汇总总数量
}
return res; //返回结果
}
int main() {
ll l, r;
scanf("%lld%lld", &l, &r);
printf("%lld\n", calc(r) - calc(l - 1));
return 0;
}

View File

@@ -0,0 +1,100 @@
## $1799$. [$Ahoi2009$] $self$ 同类分布
[题目链接](https://hydro.ac/d/bzoj/p/1799)
[洛谷链接](https://www.luogu.com.cn/problem/P4127)
### 一、题目描述
给出 $a,b$,求出$[a,b]$中各位数字之和能整除原数的数的数字个数。
其中$1≤a≤b≤10^{18}$
### 二、解题思路
我们来思考此题目的状态表示应该和哪些因素有关:
* 数位$pos$
这个是最无脑的,数位$dp$都得有这个。
* 数字前序$pre$(十进制表示)
* 数位和$sum$
<font color='red' size=4><b>枚举数位和</b></font>
因为总共就$18*9=162$,然后问题就变成了一个数$\%mod=0$,$mod$是枚举的。想到这两个也不是什么难事,最终需要我们计算原数是否能整除掉数位和,不记录数位和记录啥?
>> 想想状态:$dp[pos][sum][pre]$,当前$pos$位上数位和是$sum$,$pre$就是在算这个数$\%mod$,(从高位算 $*10+i$),因为我们枚举的数要保证数位和等于$mod$,还要保证这个数是$mod$的倍数,很自然就能找到这些状态。
<font color='red' size=4><b>此题的难点</b></font>
$pre$是必须要携带的信息,否则最后无法计算是不是能整除掉$sum$,但是,由于$pre$最终就是原来的数字,范围是$<=1e18$,很显然不能做为状态,而且,如果做了状态,也就起不到按集合聚合结果的作用,就真的成了无法记忆化了。
所以,我们需要想办法把$pre$化成集合的形式!
<font color='blue' size=4><b>套路</b></font>
一般的,如果题目中有**取模**这样的含义,通常是按**模**分组,也就是按**模**进行划分集合。叫套路有些技术含量太$low$,那就叫做**经验**吧~
如此,我们在每一步计算出$pre$时,就不断的在运算过程中取模再保存状态,成功的将单个数字化为集合形式~
背后的原因:最后的目标并不是真的需要$pre$,而是需要问$ sum \mid pre$ 还是 $sum \nmid pre$!我们可以理解为不用真的传递原始**类前缀和**(就让我先这样叫它吧,含义你懂的),而是传递了类前缀和$\% ~ mod$的值,这样,如果$pre$中有整数个$mod$,就会被拿下,只保留余数,但并不影响最终判断是不是能整除$mod$,好处就是每次传递的数字一下就下降到$mod-1$及以下,没有溢出等烦恼。体现在本题中,如果我们傻傻的强行设计状态$$\large f[20][N][1e18]$$
不就$MLE$炸掉了吗?当然,这样也没法按集合思路进行记忆,肯定是不中的,需要对$pre \% mod$进行分组~!
<font color='blue' size=4><b>其它思路</b></font>
显然对于每一个$mod$,$pre$不能保证状态唯一,这是你要是想加一维$dp[pos][sum][val][mod]$,记录每一个$mod$的状态(这里$sum$可以用减法,然而$val$不行,就只能加一维),那你就想太多了,这样是会超时的(因为状态太多,记忆化效果不好)。这里直接对每一个$mod$,$memset$一次就能$ac$。
### 三、实现代码
```c++
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
typedef long long ll;
const int N = 170;
int a[20], mod;
/*
f[pos][sum][pre]
第一维:数字的每一位,从18~1有效
第二维:已经走完的路径,每位的数位和
第三维:已经走完的路径,每位的拼接值(十进制)
前提:已经走到pos这个位置,前面积累下来的数位和是sum,前面的数字拼接pre(10进制), (是不是贴上界)
返回:在满足前提条件下,后续符合题意的数字有多少个?
*/
ll f[20][N][N];
//此处注意一下:pre如果是类型ll,则会第9个点TLE,是类型int,则可以AC!
ll dfs(int pos, int sum, int pre, bool limit) {
// 如果走完了全程,pre=原数%mod,如果最后pre还能整除mod,并且,sum等于mod,则贡献数字1个
if (pos == 0) return pre == 0 && sum == mod;
if (!limit && ~f[pos][sum][pre]) return f[pos][sum][pre];
ll ans = 0;
int up = limit ? a[pos] : 9;
for (int i = 0; i <= up; i++) {
// sum+i:数位和+i
// (pre * 10 + i) % mod : 不断的拼接前缀数字(十进制),但不能盲目记录原始数字,需要分组,按什么分组呢?当然是按枚举的当前mod分组
// 上面这句是本题的精华!
ans += dfs(pos - 1, sum + i, (pre * 10 + i) % mod, limit && i == a[pos]);
}
//记忆化结果
if (!limit) f[pos][sum][pre] = ans;
return ans;
}
ll calc(ll x) {
if (x == -1) return 0; //处理一下边界情况
int al = 0;
while (x) a[++al] = x % 10, x /= 10;
ll res = 0;
for (mod = 1; mod <= 9 * al; mod++) { // max(mod)=9*18=162,数量并不大,可以枚举
memset(f, -1, sizeof f); //每次取的模都会变化,这样状态数组就不再可以重用,需要每次清空
res += dfs(al, 0, 0, true); //其实本题的本质是按分组讨论的方式完成的数量的统计,然后再汇总总数量
}
return res; //返回结果
}
int main() {
ll l, r;
scanf("%lld%lld", &l, &r);
printf("%lld\n", calc(r) - calc(l - 1));
return 0;
}
```

View File

@@ -0,0 +1,61 @@
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 20;
/*
x,y <= 10^{18},用10进制拆分到数组a,20位足够
本题对于内存空间的限制比较极限,如果无脑使用了32,就会使得f的容量超限
32*32*10010=10250240 byte = 10010 kb = 9.775390625 mb
内存上限是:65535kb=64mb
因为C++本身运行还需要一次的内存,64MB是很极限的,所以在多维数组创建时,一定要注意维度的上界
*/
LL a[N];
/*
pos:走到哪个数位,长度最长是N=20
k:以哪个数位为平衡数位,可以是1到20
sum:前面数位的数乘以偏移距离的和,最大偏移量是20,数位最大是9
可以视为
10*20+10*19+....+10*1=10*(1+2+3+...+20)=10*21*10=2100
*/
LL f[N][N][2110];
LL dfs(int pos, int k, int sum, bool lead, bool limit) {
if (pos == 0) return sum == 0;
if (sum < 0) return 0;
if (!lead && !limit && ~f[pos][k][sum]) return f[pos][k][sum];
int up = limit ? a[pos] : 9;
LL ans = 0;
for (int i = 0; i <= up; i++)
// sum + (pos - k) * i : 这句话很牛X,pos > k 是加,pos < k是减
ans += dfs(pos - 1, k, sum + (pos - k) * i, lead && i == 0, limit && i == a[pos]);
if (!limit && !lead) return f[pos][k][sum] = ans;
return ans;
}
LL calc(LL x) {
if (x == -1) return 0; //如果输入的是l=0,则返回0
int al = 0;
while (x) a[++al] = x % 10, x /= 10;
LL ans = 0;
for (int i = al; i; i--) //枚举每一个数位,模拟此位置k为平衡数位
ans += dfs(al, i, 0, true, true);
// 00,000,0000......
return ans - al + 1;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
//平衡数是数的本身性质,与给定的区间无关,可以将memset放在循环外初始化
memset(f, -1, sizeof f);
int T;
cin >> T;
LL l, r;
while (T--) {
cin >> l >> r;
printf("%lld\n", calc(r) - calc(l - 1));
}
return 0;
}

View File

@@ -0,0 +1,87 @@
## $HDU$ $3709$ $Balanced$ $Number$
[题目传送门](http://acm.hdu.edu.cn/showproblem.php?pid=3709)
### 一、题目描述
题目描述:求区间$[l,r]$里面满足**平衡数**的数的个数
**平衡数**:可以通过找一个平衡数位,该数位左边的数位乘以偏移距离的和等于右边的数位乘以偏移距离的和。
举个栗子:$4139$,平衡数位为$3$,$4*2+1=9$,因此该数是平衡数。
### 二、解题思路
定义状态$dp[pos][k][sum]$表示枚举到$pos$位置,当前平衡数位位置为$k$,前面数位的数乘以偏移距离的和。
最后判断一下是否等于$0$就行了。
我们可以枚举平衡数位的位置,同时在枚举时维护和就行了。
另外,这个题是做到的第一个前导$0$有影响的题
如果数为$0$,是可以的(既符合题意,又确实存在这个数)
如果数为$00$,是不可以的(符合题意,是平衡数,但是这个数跟$0$不是一个吗)
所以这个数有几位,数位最大是$len$,就会多统计了$len-1$个,在计算的时候要除掉
$00,000,0000......$
### 三、实现代码
```c++
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 20;
/*
x,y <= 10^{18},用10进制拆分到数组a,20位足够
本题对于内存空间的限制比较极限,如果无脑使用了32,就会使得f的容量超限
32*32*10010=10250240 byte = 10010 kb = 9.775390625 mb
内存上限是:65535kb=64mb
因为C++本身运行还需要一次的内存,64MB是很极限的,所以在多维数组创建时,一定要注意维度的上界
*/
LL a[N];
/*
pos:走到哪个数位,长度最长是N=20
k:以哪个数位为平衡数位,可以是1到20
sum:前面数位的数乘以偏移距离的和,最大偏移量是20,数位最大是9
可以视为
10*20+10*19+....+10*1=10*(1+2+3+...+20)=10*21*10=2100
*/
LL f[N][N][2110];
LL dfs(int pos, int k, int sum, bool lead, bool limit) {
if (pos == 0) return sum == 0;
if (sum < 0) return 0;
if (!lead && !limit && ~f[pos][k][sum]) return f[pos][k][sum];
int up = limit ? a[pos] : 9;
LL ans = 0;
for (int i = 0; i <= up; i++)
// sum + (pos - k) * i : 这句话很牛X,pos > k 是加,pos < k是减
ans += dfs(pos - 1, k, sum + (pos - k) * i, lead && i == 0, limit && i == a[pos]);
if (!limit && !lead) return f[pos][k][sum] = ans;
return ans;
}
LL calc(LL x) {
if (x == -1) return 0; //如果输入的是l=0,则返回0
int al = 0;
while (x) a[++al] = x % 10, x /= 10;
LL ans = 0;
for (int i = al; i; i--) //枚举每一个数位,模拟此位置k为平衡数位
ans += dfs(al, i, 0, true, true);
//0,00,000,0000,....
return ans - al + 1;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
//平衡数是数的本身性质,与给定的区间无关,可以将memset放在循环外初始化
memset(f, -1, sizeof f);
int T;
cin >> T;
LL l, r;
while (T--) {
cin >> l >> r;
printf("%lld\n", calc(r) - calc(l - 1));
}
return 0;
}
```

View File

@@ -0,0 +1,250 @@
## $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;
}
```

View File

@@ -0,0 +1,25 @@
#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;
}

View File

@@ -0,0 +1,52 @@
#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;
}

View File

@@ -0,0 +1,35 @@
#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(5) << endl;
cout << f2(12345) << endl;
//极大值是1e9-1=99999999
cout << f2(1e9 - 1) << endl;
//输出4599
return 0;
}

View File

@@ -0,0 +1,62 @@
#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;
}

View File

@@ -0,0 +1,60 @@
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
const int N = 32;
//整数范围,最大数字的二进制表示,有32位也足够
//状态范围:因为数字0的个数与数字1的个数的差,在中间过程中可能是负数,所以需要做一个偏移处理,映射到自然数的下标空间,也就是双倍空间足够
int f[N][N << 1];
int a[N];
int dfs(int pos, int st, bool lead, bool limit) {
if (pos == 0) return st >= 32; //大于等于偏移量32,也就是count(0)-count(1)>=0,此时贡献数字1个
if (!limit && !lead && ~f[pos][st]) return f[pos][st];
int up = limit ? a[pos] : 1;
int ans = 0;
for (int i = 0; i <= up; i++) {
//前一位是前导零的情况下,本位依然是0
//(1)st不变,继续传送偏移量32,也就是count(0)-count(1)=0
//(2)则需要继续传递前导零标识
if (lead && i == 0)
ans += dfs(pos - 1, st, true, limit && i == a[pos]); //有前导零就不统计在内
else
//场景1:前一位是前导零,本位不是0
//场景2:前一位不是前导零,本位是0
//场景3:前一位不是前导零,本位不是0
//此三种场景,都不需要继续传递前导零标识,即lead=false
//(1)如果本位是0,则传递st+1
//(2)如果本位是1,则传递st-1
ans += dfs(pos - 1, st + (i == 0 ? 1 : -1), false, limit && i == a[pos]);
}
if (!limit && !lead) f[pos][st] = ans;
return ans;
}
int calc(int x) {
int al = 0;
while (x) {
a[++al] = x & 1; //取出二进制的每一位表示,存入数组a中
x >>= 1;
}
// pos=al:从al位开始
// st=32:目前count(0)-count(1)=32,也就是默认加上了偏移量32
// lead=true:需要考虑前导0
// limit=true:贴上界
return dfs(al, 32, true, true);
}
int main() {
memset(f, -1, sizeof f);
// 1 ≤ Start < Finish ≤ 2,000,000,000
// INT_MAX=2147483647,看来Start和Finish还是在整数范围内
int a, b;
while (cin >> a >> b)
printf("%d\n", calc(b) - calc(a - 1));
return 0;
}

View File

@@ -0,0 +1,77 @@
## $POJ$ $3252$(组合数)
[题目传送门](http://poj.org/problem?id=3252)
### 一、题目描述
给出两个整数$Start,Finish$,问区间$[Start,Finish]$中 <font color='red' size=4><b>$round$ $number$</b></font>的个数。
($1<=Start<Finish<=2000000000$)
$round$ $number$定义:$RN$数即化为二进制后$0$的个数不少于$1$的个数的数。(<font color='blue' size=4><b>需要去除前导零</b></font>)
### 二、解题思路
这题的约束就是一个数的二进制中$0$的数量要不能少于$1$的数量,通过上一题,这题状态就很简单了,$dp[pos][num]$,到当前数位$pos$,$0$的数量减去$1$的数量不少于$num$的方案数,一个简单的问题,中间某个$pos$位上$num$可能为负数(这不一定是非法的,因为我还没枚举完嘛,只要最终的$num>=0$才能判合法,中途某个$pos$就不一定了),这里比较好处理,$Hash$嘛,最小就$-32$吧,直接加上$32$,把$32$当$0$用。这题主要是要想讲一下$lead$的用法,显然我要统计$0$的数量,前导零是有影响的。至于$!lead\&\&!limit$才能$dp$,都是类似的,自己慢慢体会吧。
### 三、实现代码
```c++
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
const int N = 32;
//整数范围,最大数字的二进制表示,有32位也足够
//状态范围:因为数字0的个数与数字1的个数的差,在中间过程中可能是负数,所以需要做一个偏移处理,映射到自然数的下标空间,也就是双倍空间足够
int f[N][N << 1];
int a[N];
int dfs(int pos, int st, bool lead, bool limit) {
if (pos == 0) return st >= 32; //大于等于偏移量32,也就是count(0)-count(1)>=0,此时贡献数字1个
if (!limit && !lead && ~f[pos][st]) return f[pos][st];
int up = limit ? a[pos] : 1;
int ans = 0;
for (int i = 0; i <= up; i++) {
//前一位是前导零的情况下,本位依然是0
//(1)st不变,继续传送偏移量32,也就是count(0)-count(1)=0
//(2)则需要继续传递前导零标识
if (lead && i == 0)
ans += dfs(pos - 1, st, true, limit && i == a[pos]); //有前导零就不统计在内
else
//场景1:前一位是前导零,本位不是0
//场景2:前一位不是前导零,本位是0
//场景3:前一位不是前导零,本位不是0
//此三种场景,都不需要继续传递前导零标识,即lead=false
//(1)如果本位是0,则传递st+1
//(2)如果本位是1,则传递st-1
ans += dfs(pos - 1, st + (i == 0 ? 1 : -1), false, limit && i == a[pos]);
}
if (!limit && !lead) f[pos][st] = ans;
return ans;
}
int calc(int x) {
int al = 0;
while (x) {
a[++al] = x & 1; //取出二进制的每一位表示,存入数组a中
x >>= 1;
}
// pos=al:从al位开始
// st=32:目前count(0)-count(1)=32,也就是默认加上了偏移量32
// lead=true:需要考虑前导0
// limit=true:贴上界
return dfs(al, 32, true, true);
}
int main() {
memset(f, -1, sizeof f);
// 1 ≤ Start < Finish ≤ 2,000,000,000
// INT_MAX=2147483647,看来Start和Finish还是在整数范围内
int a, b;
while (cin >> a >> b)
printf("%d\n", calc(b) - calc(a - 1));
return 0;
}
```

View File

@@ -0,0 +1,94 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 32; // 输入的数据范围2^31-1,也就是整数上界。2进制是最小的进制,32也够了
int a[N], al;
int f[N][N];
/*
f[i][j]:
i:所在数位第几位,比如469,出发时,就是站在第3位,即4这个位置出发,一般从高位到低位进行,起始值是最高位
j:st的意思,配合位置i,描述一下当前的情况
举个栗子: f[3][4]:走到了位3这个数位,前面已经取得了4个是1的数位,此时,后续的符合条件条件的数有f[3][4]个
第二维还是因题而异的,需要针对不同题目进行思考分析。
*/
/*
功能:计算以当前状态出发,会收集到多少个符合条件的数
参数:
u :当前是第几位,比如 421,开始的时候,就是第3位,数值是4
st :记录状态传递变量,比如数字1出现的次数
lead :需不需要考虑前导零
op :是否贴上界
*/
int dfs(int u, int st, bool lead, bool op) {
// 递归边界,既然是按位枚举,最低位是1,那么u==0说明这个数我枚举完了
if (u == 0) return 1; /*这里一般返回1,表示你枚举的这个数是合法的,
那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到u位,
一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */
// 第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
// 不贴上界,不需要考虑前导零,以前计算过,这样的东西才能拿来即用
if (!op && !lead && ~f[u][st]) return f[u][st];
/*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应,具体为什么是有条件的记忆化后面会讲*/
int up = op ? a[u] : 9; // 根据limit判断枚举的上界up;这个的例子前面用213讲过了
int ans = 0;
// 开始计数
for (int i = 0; i <= up; i++) { // 枚举,把不同情况的个数加到ans就可以了
// 这里可以加一些减枝之类的代码
/*
代码细节:
==的运算优先级 高于 && ,所以先判断 i == a[pos]是第一步,与 op 相 && 是第二步
pos-1:这玩意是从高位到低位的,由大到小枚举,所以和平常的dfs有点区别,是pos-1
逐句解读:
lead && i == 0:
如果前面考虑前导0,现在i ==0 ,则后面的数字枚举,仍然要考虑前导零。
如果前面考虑前导0,现在i>0,则后面的数字枚举,不需要考虑前导零。
如果前面不考虑前导0,后面就不用考虑这个前导零的问题。
op && i == a[u] :
如果原来op=true,即贴上界,而且,当前位枚举的数字i也和原数字位一致,那么后面的数字枚举,必然也继续贴上界
如果原来op=false,就是原来就不贴上界,越往后也不会贴上界了。
*/
st = st + i; // 这句话是灵活的,因题而异,一般是描述传递状态的变更,比如选择当前数位的数字1,就多了一个1,需要+1等等
ans += dfs(u - 1, st, lead && i == 0, op && i == a[u]);
/*这里还算比较灵活,不过做几个题就觉得这里也是套路了
大概就是说,我当前数位枚举的数是i,然后根据题目的约束条件分类讨论
去计算不同情况下的个数,还有要根据st变量来保证i的合法性,比如题目
要求数位上不能有62连续出现,那么就是st就是要保存前一位pre,然后分类,
前一位如果是6那么这意味就不能是2,这里一定要保存枚举的这个数是合法
*/
}
// 计算完,记录状态
if (!op && !lead) f[u][st] = ans;
/*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
return ans;
}
// 因为用前缀和思想,所以要计算r和l-1两次,封成一个calc函数。
int calc(int x) {
al = 0; // 注意清零,al清零即可,a不用memset清零
// 把数位都分解出来
while (x) a[++al] = x % 10, x /= 10; // 个人喜欢编号为[1,al]
return dfs(al, 0, true, true); // 从最高位开始枚举,刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
}
int main() {
int l, r;
while (cin >> l >> r) {
// 初始化dp数组为-1
memset(f, -1, sizeof f);
printf("%lld\n", calc(r) - calc(l - 1));
}
return 0;
}