题面
题面描述
由于SZY经常在月黑风高夜游荡,他的计算器患上了精神分裂,分出了两个人格(存储单元)。一开始,第一个单元包含数字1,第二个单元包含数字0。于是,它就不能进行正常的计算了,而只支持以下两种操作:
假设第一个单元的数字为 a a a,第二个单元的数字为 b b b,那么将第二个单元的数字改成 b + 1 b+1 b+1。
假设第一个单元的数字为 a a a,第二个单元的数字为 b b b,那么将第一个单元的数字改成 a × b a\times b a×b。
现在SZY想知道,有多少个正整数 x ( l ≤ x ≤ r ) x (l\le x\le r) x(l≤x≤r) 满足存在一种方式可以让计算器从初始状态开始,操作不超过p步之后第一个单元中的数字为 x x x。
输入输出格式
输入格式
共一行,为三个空格隔开的整数。
输出格式
输出仅一行,包含一个整数,即为问题所求。
输入输出样例
输入数据 1
2 10 3
输出数据 1
1
输入数据 2
2 111 100
输出数据 2
106
输入数据 3
2 111 11
输出数据 3
47
数据规模
对于100%的数据, 2 ≤ l ≤ r ≤ 1 0 9 , 1 ≤ p ≤ 100 2\le l\le r\le10^9, 1\le p\le100 2≤l≤r≤109,1≤p≤100
题解
思路
我们可以轻易地发现, 对于一个数, 如果它含有大于
p
p
p的质因数, 那么这个数一定不能被选中
所以我们可以使用
d
f
s
dfs
dfs来找出所有(
≤
r
\le r
≤r)只含小于等于
p
p
p的质因数的数
代码如下:
int num[MAXN],cnt;//能用p以内质因数表示出来的数
bool vis[MAXN];
int prime[100],tot=0;
void getpri(){//素数筛
for(int x=2;x<=p;x++){
if(!vis[x]){
prime[++tot]=x;
}
for(int y=1;y<=tot;y++){
vis[x*prime[y]]=1;
if(x%prime[y]==0){
break;
}
}
}
return;
}
void dfs(int i,int num_now){
num[++cnt]=num_now;
// cout << cnt << ':' << i << ' ' << num_now << endl;
for(int x=i;x<=tot;x++){//从i开始, 防止i之前的造成重复
if(num_now*prime[x]<=r){
dfs(x,num_now*prime[x]);
}else{
break;
}
}
}
根据我的严谨的计算(其实就是把最大值带入输出),
c
n
t
(
符合上述所说数的个数
)
cnt_{(符合上述所说数的个数)}
cnt(符合上述所说数的个数)的最大值为
2944730
2944730
2944730, 即为
3
×
1
0
6
3\times 10^6
3×106即可, 这就是MAXN 的大小
现在, n u m num num数组里存的都是只含有 p p p以内因数的数, 现在我们要在其中找出能在 p p p步内走出的数
我们定义一个数组
v
v
i
s
[
y
]
vvis[y]
vvis[y], 表示
n
u
m
[
y
]
num[y]
num[y]是否被选中,
(注:
v
v
i
s
vvis
vvis中的
y
y
y记录的是下标, 而不是一个具体的数, 其对应的是
n
u
m
[
y
]
num[y]
num[y].
我们再定义一个状态
d
p
[
y
]
dp[y]
dp[y], 表示
n
u
m
[
y
]
num[y]
num[y]需要多少次乘法到达(
a
=
a
×
b
a=a\times b
a=a×b操作).
(注: 同上,
y
y
y同样也只是一个下标)
现在枚举计算器的第二人格
x
x
x(即题目中的
b
b
b)作为倍数, 再枚举一个基本数z(
∈
n
u
m
\in num
∈num, 以下标形式枚举), 乘起来得到一个新数
y
y
y如下:
n
u
m
[
y
]
=
n
u
m
[
z
]
×
x
num[y]=num[z]\times x
num[y]=num[z]×x
于是我们可以轻易地推出以下方程式:
d
p
[
y
]
=
m
i
n
(
d
p
[
y
]
,
d
p
[
z
]
+
1
)
(
多使用一次乘法
)
dp[y]=min(dp[y],dp[z]+1)_{(多使用一次乘法)}
dp[y]=min(dp[y],dp[z]+1)(多使用一次乘法)
但是,
n
u
m
[
y
]
num[y]
num[y]需要满足以下条件, 才能认定它是合法的
- l ≤ n u m [ y ] l\le num[y] l≤num[y]
-
n
u
m
[
y
]
≤
r
num[y]\le r\;\;
num[y]≤r(也可以表示为
y
≤
c
n
t
y\le cnt
y≤cnt)
以上两点是为了这个数在 l , r l,r l,r范围内 -
v
i
s
[
y
]
=
=
0
vis[y]==0
vis[y]==0
这个数没有被选过 -
d
p
[
y
]
+
x
≤
p
dp[y]+x\le p
dp[y]+x≤p
总步数在 p p p以内, 我们定义的 d p [ y ] dp[y] dp[y]是使用乘法的次数, x x x既是使用加法的次数
只要符合以上四个条件, 就可以累加答案了(^_^)
AC Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define MAXN 2944730+100
bool vis[10086],vvis[MAXN];
int prime[100],tot=0;
int l,r,p;
int dp[MAXN],b[MAXN];
int num[MAXN],cnt;//能用100以内质因数表示出来的数
void getpri(){
for(int x=2;x<=p;x++){
if(!vis[x]){
prime[++tot]=x;
}
for(int y=1;y<=tot;y++){
vis[x*prime[y]]=1;
if(x%prime[y]==0){
break;
}
}
}
return;
}
void dfs(int i,int num_now){
num[++cnt]=num_now;
// cout << cnt << ':' << i << ' ' << num_now << endl;
for(int x=i;x<=tot;x++){
if(num_now*prime[x]<=r){
dfs(x,num_now*prime[x]);
}else{
break;
}
}
}
signed main(){
cin >> l >> r >> p;
getpri();
dfs(1,1);
sort(num+1,num+1+cnt);
memset(dp,0x7f,sizeof(dp));
dp[1]=0;
vvis[1]=1;
int ans=0;
for(int x=2;x<=p;x++){
int y=1;
for(int z=1;z<=cnt;z++){
bool bol=0;
while(num[y]!=num[z]*x){
y++;
if(y>cnt){
bol=1;
break;
}
}
if(bol){
break;
}
dp[y]=min(dp[y],dp[z]+1);
if(dp[y]+x<=p && num[y]>=l && vvis[y]==0){
vvis[y]=1;
ans++;
}
}
}
cout << ans;
return 0;
}
补充
如果上面对
n
u
m
[
y
]
num[y]
num[y]的第四个约束条件没看懂的话, 可以参考下图:
让
a
a
a增加的途径只有
a
×
b
a\times b
a×b, 让
b
b
b增加的途径只有
b
+
1
b+1
b+1
所以
d
p
[
y
]
+
x
是需要的总步数
dp[y]+x是需要的总步数
dp[y]+x是需要的总步数