文章目录
- 数位DP
- 大量例题
- P4999 - 烦人的数学作业
- HDU2089 - 不要62
- P6218 - [USACO06NOV] Round Numbers S
- P2602 - [ZJOI2010]数字计数
- P2657 - [SCOI2009] windy 数
- P4317 - 花神的数论题
- CF855E Salazar - Slytherin's Locket
- SP10606 - BALNUM - Balanced Numbers
- P4124 - [CQOI2016]手机号码
- CF1073E - Segment Sum
- CF55D - Beautiful numbers
- P4127 - [AHOI2009]同类分布
- P3413 - SAC#1 - 萌数
- !!
数位DP
大量例题
P4999 - 烦人的数学作业
题目描述:
给出 L , R L,R L,R,求在 [ L , R ] [L,R] [L,R] 中的所有整数的数位和的和(例如 123 123 123 的数位和= 1 + 2 + 3 = 6 1+2+3=6 1+2+3=6)
1 ≤ L ≤ R ≤ 1 0 18 1 \leq L \leq R \leq 10^{18} 1≤L≤R≤1018
思路: 板子
我们搜的话只需要记录当前位数位
p
o
s
pos
pos 和数位和
s
u
m
sum
sum 即可,因为这道题我们不在乎数的具体组成结构,只在乎某一段在值域上的总体情况。比如我们当前这一段数位和为
s
u
m
sum
sum,我们最后变成
a
n
s
ans
ans,中间加了一段数位和为
a
n
s
−
s
u
m
ans-sum
ans−sum 的,显然前后两端不管构造如何,都不影响,只有值域(数位和)能影响
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int p=1e9+7;
int a[25],len=0;
ll dp[200][25];
ll dfs(bool limit,int sum,int pos){
if(pos>len) return sum;
if(!limit&&~dp[sum][pos]) return dp[sum][pos];
int cur=limit?a[len-pos+1]:9;
ll res=0;
for(int i=0;i<=cur;i++){
res=(res+dfs(i==cur&&limit,sum+i,pos+1))%p;
}
if(!limit) dp[sum][pos]=res;
return res;
}
ll part(ll x){
len=0;
while(x) a[++len]=x%10,x/=10;
memset(dp,-1,sizeof dp);
return dfs(1,0,1);
}
int main(){
int t;
cin>>t;
while(t--){
ll l,r;
cin>>l>>r;
cout<<(part(r)-part(l-1)+p)%p<<endl;
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
HDU2089 - 不要62
题目描述:
定义包含 4 4 4 或 62 62 62(其中 62 62 62 必须为连号) 为不吉利的数字,给出多组 n n n 和 m m m,求 [ n , m ] [n,m] [n,m] 中吉利的数字个数
0 ≤ n ≤ m ≤ 1 0 6 0 \leq n \leq m \leq 10^6 0≤n≤m≤106
思路:
这里我选择搜不吉利的数个数然后减掉,实际上直接搜吉利个数更简单,只需要对
4
4
4 和
62
62
62 的情况剪枝即可,接着就是求数的个数。。
code:
int n,m;
int len=0,a[15];
int dp[20][25][2];
int dfs(int pre,int pos,int sum,bool vis,bool limit){
if(pos>len) return sum;
if(!limit&&~dp[pre][pos][vis]) return dp[pre][pos][vis];
int cur=limit?a[len-pos+1]:9;
int res=0;
for(int i=0;i<=cur;i++){
bool f=0;
int add=0;
if((i==4)||(pre==6&&i==2)) f=1;
if((!vis)&&f) add=1;
res+=dfs(i,pos+1,sum+add,vis||add,limit&&i==cur);
}
if(!limit) dp[pre][pos][vis]=res;
return res;
}
int part(int x){
memset(dp,-1,sizeof dp);
len=0;
while(x) a[++len]=x%10,x/=10;
return dfs(0,1,0,0,1);
}
int main(){
while(cin>>n>>m){
if(n==0&&m==0) return 0;
cout<<m-n+1-part(m)+part(n-1)<<endl;
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
P6218 - [USACO06NOV] Round Numbers S
题目描述:
如果一个正整数的二进制表示中,
0
0
0 的数目不小于
1
1
1 的数目,那么它就被称为 「圆数」。
计算区间
[
l
,
r
]
[l,r]
[l,r] 中有多少个 「圆数」。
1
≤
l
,
r
≤
2
⋅
1
0
9
1 \leq l,r \leq 2 \cdot 10^9
1≤l,r≤2⋅109
思路:
先把数拆分成二进制,然后记录
0
,
1
0,1
0,1 的个数即可,注意前导零
这里我没用用前导零标记,而是直接用
c
n
t
1
cnt1
cnt1 的个数判断当前是不是前导零
code:
int l,r;
int len=0,a[15];
int dp[35][35][35];
int dfs(int pos,int cnt0,int cnt1,bool limit){
if(pos>len) return cnt0>=cnt1;
if(!limit&&~dp[pos][cnt0][cnt1]) return dp[pos][cnt0][cnt1];
int cur=limit?a[len-pos+1]:1;
int res=0;
for(int i=0;i<=cur;i++){
res+=dfs(pos+1,cnt0+(i==0&&(cnt1!=0)),cnt1+(i==1),limit&&i==cur);
}
if(!limit) dp[pos][cnt0][cnt1]=res;
return res;
}
int part(int x){
len=0;
memset(dp,-1,sizeof dp);
while(x) a[++len]=x&1,x>>=1;
return dfs(1,0,0,1);
}
int main(){
cin>>l>>r;
if(l) cout<<part(r)-part(l-1)<<endl;
else cout<<part(r)<<endl;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
P2602 - [ZJOI2010]数字计数
题目描述:
给定两个正整数 a a a 和 b b b,求在 [ a , b ] [a,b] [a,b] 中的所有整数中,每个数码 ( d i g i t ) (digit) (digit)各出现了多少次
1 ≤ a ≤ b ≤ 1 0 12 1 \leq a \leq b \leq 10^{12} 1≤a≤b≤1012
思路: 板子,每个数码分开搜就行
code:
int len,a[30];
ll dp[205][30];
int dig;
ll solve(int pos,bool limit,ll sum,bool lead){
if(pos>len) return sum;
if(!limit&&!lead&&~dp[sum][pos]) return dp[sum][pos];
int cur=limit?a[len-pos+1]:9;
ll res=0;
for(int i=0;i<=cur;i++){
if(lead&&!i) res+=solve(pos+1,limit&&i==cur,sum,1);
else if(lead&&i) res+=solve(pos+1,limit&&i==cur,sum+(i==dig),0);
else res+=solve(pos+1,limit&&i==cur,sum+(i==dig),0);
}
if(!lead&&!limit) dp[sum][pos]=res;
return res;
}
ll part(ll x){
memset(dp,-1,sizeof dp);
len=0;
while(x) a[++len]=x%10,x/=10;
return solve(1,1,0,1);
}
int main(){
ll l,r;
cin>>l>>r;
for(int i=0;i<=9;i++){
dig=i;
cout<<part(r)-part(l-1)<<" ";
}
puts("");
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
P2657 - [SCOI2009] windy 数
题目描述:
不含前导零且相邻两个数字之差至少为
2
2
2 的正整数被称为
w
i
n
d
y
windy
windy 数。
问在
[
L
,
R
]
[L,R]
[L,R] 之间有多少
w
i
n
d
y
windy
windy 数?
思路:
这题需要判断前导零,只有进入有效数位只有才开始前后数位差的统计
code:
int L,R;
int len=0,a[15];
int dp[15][10][15];
int dfs(int pos,int pre,int d,bool lead,bool limit){
if(d<2) return 0;
if(pos>len) return 1;
if(!limit&&!lead&&~dp[pos][pre][d]) return dp[pos][pre][d];
int cur=limit?a[len-pos+1]:9,res=0;
for(int i=0;i<=cur;i++){
if(lead&&!i) res+=dfs(pos+1,pre,d,1,limit&&(i==cur));
else if(lead&&i) res+=dfs(pos+1,i,d,0,limit&&(i==cur));
else res+=dfs(pos+1,i,min(d,abs(i-pre)),0,limit&&(i==cur));
}
if(!limit&&!lead) dp[pos][pre][d]=res;
return res;
}
int part(int x){
memset(dp,-1,sizeof dp);
len=0;
while(x) a[++len]=x%10,x/=10;
return dfs(1,0,10,1,1);
}
int main(){
scanf("%d %d",&L,&R);
printf("%d\n",part(R)-part(L-1));
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
P4317 - 花神的数论题
题面描述:
定义 s u m ( i ) sum(i) sum(i) 代表 i i i 的二进制表示中 1 1 1 的个数,计算 ∏ i = 1 n s u m ( i ) \prod_{i=1}^nsum(i) ∏i=1nsum(i)对 1 e 7 + 7 1e7+7 1e7+7 取模的结果
1 ≤ n ≤ 1 0 15 1 \leq n \leq 10^{15} 1≤n≤1015
思路:
做法 1 1 1:分别计算二进制中 1 1 1 的个数为 i i i 的数的个数,然后用快速幂计算答案 ∏ i c n t i \prod i^{cnt_i} ∏icnti,注意模数是非质数,在统计 c n t i cnt_i cnti 不可取模
做法 2 2 2:直接搜答案,返回的是定义状态下的乘积
code1:
const int p=10000007;
ll n,k;
int len=0,a[60];
ll dp[65][65];
ll dfs(int pos,int cnt,bool limit){
if(cnt>k) return 0;
if(pos>len) return cnt==k;
if(!limit&&~dp[pos][cnt]) return dp[pos][cnt];
int cur=limit?a[len-pos+1]:1;
ll res=0;
for(int i=0;i<=cur;i++){
res+=dfs(pos+1,cnt+(i==1),limit&&(i==cur));
}
if(!limit) dp[pos][cnt]=res;
return res;
}
ll solve(){
memset(dp,-1,sizeof dp);
return dfs(1,0,1);
}
ll qpow(ll a,ll b){
ll res=1;
while(b){
if(b&1) res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
int main(){
cin>>n;
while(n) a[++len]=n&1,n>>=1;
ll ans=1;
for(int i=1;i<=60;i++){
k=i;
ll tmp=solve();
ans=ans*qpow(i,tmp)%p;
}
cout<<ans<<endl;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
code2:
ll dfs(int pos,int cnt,bool limit){
if(pos>len) return max(1,cnt);
if(!limit&&~dp[pos][cnt]) return dp[pos][cnt];
int cur=limit?a[len-pos+1]:1;
ll res=1;
for(int i=0;i<=cur;i++){
res=res*dfs(pos+1,cnt+(i==1),limit&&(i==cur))%p;
}
if(!limit) dp[pos][cnt]=res;
return res;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
CF855E Salazar - Slytherin’s Locket
题目描述:
存在
q
q
q 组询问
每次给出
l
,
r
l,r
l,r,求
[
l
,
r
]
[l,r]
[l,r] 之间转成
b
b
b 进制后,所有数码都出现偶数次的数的个数
1 ≤ l ≤ r ≤ 1 0 18 , 1 ≤ q ≤ 1 0 5 1 \leq l \leq r \leq 10^{18},1 \leq q \leq 10^5 1≤l≤r≤1018,1≤q≤105
思路:
怎么搜很显然,用一个
m
a
s
k
mask
mask 变量记录每个数码出现的奇偶即可,注意前导零。
但是对于 q q q 组询问,每次都清空显然复杂度高达 O ( q ⋅ 2 b l o g n ) O(q \cdot2^b logn) O(q⋅2blogn),这里可以使用一个小优化,就是数组多一维来记录每个进制下的状态的方案数。复杂度 O ( q ⋅ 2 b b l o g n ) O(q \cdot 2^bb logn) O(q⋅2bblogn)
这里要注意这种记录方式
p
o
s
pos
pos 这一位初始要赋值为
l
e
n
len
len 而不是
1
1
1.
举个例子
12123
12123
12123 和
12
12
12,如果用第一种方式去搜,显然搜到两者搜到第二位是状态完全相同,记忆化的东西也相同,而实际上后面的东西完全不同。因此我们必须要从高到底记录长度
可以按照按 b b b 排序,然后每次 b b b 变了就清空 d p dp dp 数组,这样空间省一个 b b b,时间上多一个小常数,非常划算
code:
int q,b,len=0;
int a[65];
ll l,r,dp[15][65][1<<12];
ll dfs(int pos,int mask,bool lead,bool limit){
if(!pos) return mask==0;
if(!lead&&!limit&&~dp[b][pos][mask]) return dp[b][pos][mask];
int cur=limit?a[pos]:b-1;
ll res=0;
for(int i=0;i<=cur;i++){
if(lead&&!i) res+=dfs(pos-1,0,1,limit&&(i==cur));
else res+=dfs(pos-1,mask^(1<<i),0,limit&&(i==cur));
}
if(!lead&&!limit) dp[b][pos][mask]=res;
return res;
}
ll solve(ll x){
len=0;
while(x) a[++len]=x%b,x/=b;
return dfs(len,0,1,1);
}
int main(){
memset(dp,-1,sizeof dp);
scanf("%d",&q);
while(q--){
scanf("%d %lld %lld",&b,&l,&r);
printf("%lld\n",solve(r)-solve(l-1));
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
SP10606 - BALNUM - Balanced Numbers
题目描述:
定义平衡数:当且仅当对于所有出现过的数位(即
0
−
9
0−9
0−9 ),每个偶数出现奇数次,每个奇数出现偶数次。
给定 L , R L,R L,R,请统计出 [ A , B ] [A,B] [A,B] 内所有平衡数的个数。
1 ≤ L ≤ R ≤ 1 0 19 1 \leq L \leq R \leq 10^{19} 1≤L≤R≤1019
思路:
搜的时候用两个变量分别表示各个数码有没有出现过,出现了奇数次还是偶数次,这样我们就可以很轻易的
c
h
e
c
k
check
check 最后这个数是否为平衡数。
由于两个数乘积过大,我们显然可以将两个数 h a s h hash hash 成一个数。并且可以发现这样大约 1 0 6 10^6 106 个数对有很多都是不合法的,比如某个数码可能没有出现过,那么它在第二个状态中对应位置就不可能为 1 1 1,我们可以做预处理去筛选,实际上大约只有 6 ⋅ 1 0 4 6 \cdot 10^4 6⋅104 个合法状态
code:
#include <bits/stdc++.h>
using namespace std;
typedef long long unsigned ll;
typedef pair<int,int> pii;
ll L,R;
int len=0;
int a[25];
ll dp[25][60000];
map<pii,int> mp;
int tot=0;
inline bool check(int x,int mask){
for(int i=0;i<=9;i++){
if((x&1)==0){
x>>=1;mask>>=1;
continue;
}
if(i%2==0&&!(mask&1)) return false;
if(i%2==1&&(mask&1)) return false;
x>>=1,mask>>=1;
}
return true;
}
ll dfs(int pos,int x,int mask,bool lead,bool limit){
if(!pos) return check(x,mask);
if(!limit&&!lead&&~dp[pos][mp[pii(x,mask)]]) return dp[pos][mp[pii(x,mask)]];
int cur=limit?a[pos]:9;
ll res=0;
for(int i=0;i<=cur;i++){
bool f=lead&&(i==0);
res+=dfs(pos-1,f?x:x|(1<<i),f?mask:mask^(1<<i),f,limit&&(i==cur));
}
if(!limit&&!lead) dp[pos][mp[pii(x,mask)]]=res;
return res;
}
ll solve(ll x){
len=0;
while(x) a[++len]=x%10,x/=10;
return dfs(len,0,0,1,1);
}
int main(){
for(int i=0;i<(1<<10);i++){
for(int j=0;j<(1<<10);j++){
bool f=1;
for(int k=0;k<=9;k++){
if(!(i>>k&1)&&(j>>k&1)){
f=0;break;
}
}
if(f) mp[pii(i,j)]=++tot;
}
}
memset(dp,-1,sizeof dp);
int t;
scanf("%d",&t);
while(t--){
scanf("%llu %llu",&L,&R);
printf("%llu\n",solve(R)-solve(L-1));
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
P4124 - [CQOI2016]手机号码
题目描述:
现在定义手机号码为满足以下条件的 11 11 11 位数
- 号码中要出现至少 3 3 3 个相邻的相同数字;
- 号码中不能同时出现 8 8 8 和 4 4 4。
求 [ L , R ] [L,R] [L,R] 中手机号码的个数
1 0 10 ≤ L ≤ R ≤ 1 0 11 10^{10} \leq L \leq R \leq 10^{11} 1010≤L≤R≤1011
思路:
前面几道题其实都差不多,只需要记录是否存在
4
,
8
4,8
4,8,前两位数是谁,还有是否存在过连续三位数相同即可。
并且由于这题保证 11 11 11 位数合法,我们可以直接省去前导零标记,直接从 1 − 9 1-9 1−9 作为第一位开始搜
code:
ll l,r;
int len=0,a[15];
ll dp[15][15][15][2][2][2];
ll dfs(int pos,int ppre,int pre,bool f,bool four,bool eight,bool limit){
if(four&&eight) return 0;
if(pos>len) return f;
if(!limit&&~dp[pos][ppre][pre][f][four][eight]) return dp[pos][ppre][pre][f][four][eight];
int cur=limit?a[len-pos+1]:9;
ll res=0;
for(int i=0;i<=cur;i++){
res+=dfs(pos+1,pre,i,f||((i==pre)&&(i==ppre)),four||(i==4),eight||(i==8),limit&&(i==cur));
}
if(!limit) dp[pos][ppre][pre][f][four][eight]=res;
return res;
}
ll part(ll x){
len=0;
while(x) a[++len]=x%10,x/=10;
if(len<11) return 0;
memset(&dp,-1,sizeof dp);
ll res=0;
for(int i=1;i<=a[len];i++){
res+=dfs(2,0,i,0,i==4,i==8,i==a[len]);
}
return res;
}
int main(){
cin>>l>>r;
cout<<part(r)-part(l-1)<<endl;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
CF1073E - Segment Sum
题目描述:
给出 L , R , k L,R,k L,R,k,求 [ L , R ] [L,R] [L,R] 之间数码个数不超过 k k k 的数的和
思路:
显然必须用一个
m
a
s
k
mask
mask 表示当前状态下数码的出现情况,设
c
n
t
(
x
)
cnt(x)
cnt(x) 为
x
x
x 的二进制数中
1
1
1 的个数,那么显然
c
n
t
(
x
)
>
k
cnt(x)>k
cnt(x)>k 时我们可以直接剪枝退出。
l i m i t , l e a d limit,lead limit,lead 标记照旧,如果当前数码为 0 0 0,我们只需要 l e a d lead lead 前导零标记判断是否需要加入到 m a s k mask mask 状态中
接着这题需要求的是所有数的和,发现我们无法利用现有的几个状态去记搜答案。
比如
01
01
01x 和
11
11
11x,这两个数在状态表示上相同,但是显然
11
11
11x 没法用
01
01
01x 搜出来的数,因为答案与前一部分
01
01
01 与
11
11
11 也有关,这两者在和上显然没有呈现一致性
所以我们按照每一位来算贡献,对于第 p o s pos pos 位来说,我们将该位的值定为 i i i,则会产生 1 0 l e n − p o s ∗ i ∗ [ 往 后 搜 出 的 数 的 个 数 ] 10^{len-pos}*i*[往后搜出的数的个数] 10len−pos∗i∗[往后搜出的数的个数] 的贡献,并且我们要加上往后搜出的贡献
code:
ll L,R;
int k;
int len=0,a[20];
pii dp[20][1<<11];
ll pw[20];
pii dfs(int pos,int mask,bool limit,bool lead){
if(__builtin_popcount(mask)>k) return pii(0,0);
if(pos>len) return pii(0,1);
if(!lead&&!limit&&~dp[pos][mask].first) return dp[pos][mask];
int cur=limit?a[len-pos+1]:9;
pii res=pii(0,0);
pii tmp;
for(int i=0;i<=cur;i++){
if(lead&&!i) tmp=dfs(pos+1,0,limit&&(i==cur),1);
else if(lead) tmp=dfs(pos+1,mask|(1<<i),limit&&(i==cur),0);
else tmp=dfs(pos+1,mask|(1<<i),limit&&(i==cur),0);
ll dig=i;
res.first=((pw[len-pos]*tmp.second%p*dig%p+res.first)%p+tmp.first)%p;
res.second=(res.second+tmp.second)%p;
}
if(!lead&&!limit) dp[pos][mask]=res;
return res;
}
ll part(ll x){
memset(dp,-1,sizeof dp);
len=0;
while(x) a[++len]=x%10,x/=10;
return dfs(1,0,1,1).first;
}
int main(){
pw[0]=1;
for(int i=1;i<=18;i++) pw[i]=pw[i-1]*10%p;
scanf("%lld %lld %d",&L,&R,&k);
printf("%lld\n",(part(R)-part(L-1)+p)%p);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
CF55D - Beautiful numbers
题目描述:
定义一个能被其所有非零数位整除的数为 beautiful numbers
现在给出 t t t 组询问,每次查询 [ L , R ] [L,R] [L,R] 区间内的 beautiful numbers 个数
1 ≤ t ≤ 10 , 1 ≤ L ≤ R ≤ 9 ⋅ 1 0 18 1 \leq t \leq 10,1 \leq L \leq R \leq 9 \cdot 10^{18} 1≤t≤10,1≤L≤R≤9⋅1018
思路:
很容易想到记录原数字 x x x 与包含的数位种类去做数位dp,这显然无法开下记忆化数组。
想想我们记录 x x x 的原因是为了判断这个数能不能整除它包含的数位。
也就说需要判断是否能被 { 1 , 2...9 } \{1,2...9\} {1,2...9} 的某个子集的 l c m lcm lcm 整除,这个子集的最坏情况就是 l c m { 1 , 2...9 } = 2520 lcm\{1,2...9 \}=2520 lcm{1,2...9}=2520 本身,且由于任意子集的 l c m lcm lcm 都是 2520 2520 2520 的约数,因此我们只需要记录 x m o d 2520 x \mod 2520 xmod2520 的值。
但是此时我们发现状态 20 ⋅ 2520 ⋅ 2520 20 \cdot 2520 \cdot 2520 20⋅2520⋅2520 仍然开不下数组,对于 p o s pos pos 和数位种类显然已经无法再做优化,最多把数位种类用状压表示,依然不够。。
我们发现 2520 2520 2520 的因子只有 48 48 48 个,我们在记忆化时只需要在乎这 48 48 48 个就行了,其他是谁都无所谓,所以我们可以直接 h a s h hash hash 把这一维减少为 50 50 50,可以通过本题
在函数调用的使用就正常传递 l c m lcm lcm 值代表数位种类就行,就记忆化调用数组下标时 h a s h hash hash 进去就行
code:
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int p=2520;
ll pw[25];
int a[25],len=0;
int fac[2550],o=0;
ll dp[25][50][2550];
int LCM(int a,int b){
return a*b/__gcd(a,b);
}
ll dfs(int pos,int mod,int lcm,bool limit){
if(!pos) return (mod%lcm==0)?1:0;
if(!limit&&~dp[pos][fac[lcm]][mod]) return dp[pos][fac[lcm]][mod];
int cur=limit?a[pos]:9;
ll res=0;
for(int i=0;i<=cur;i++){
res+=dfs(pos-1,(mod+pw[pos-1]*i%p)%p,i?LCM(i,lcm):lcm,limit&&(i==cur));
}
if(!limit) dp[pos][fac[lcm]][mod]=res;
return res;
}
ll solve(ll x){
len=0;
while(x) a[++len]=x%10,x/=10;
return dfs(len,0,1,1);
}
int main(){
memset(dp,-1,sizeof dp);
for(int i=1;i<=p;i++){
if(p%i==0) fac[i]=++o;
}
pw[0]=1;
for(int i=1;i<=18;i++) pw[i]=pw[i-1]*10%p;
int t;
cin>>t;
ll l,r;
while(t--){
cin>>l>>r;
cout<<solve(r)-solve(l-1)<<endl;
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
P4127 - [AHOI2009]同类分布
题目描述:
给出两个数 L , R L,R L,R,求出 [ L , R ] [L,R] [L,R] 中各位数字之和能整除原数的数的个数
1 ≤ L ≤ R ≤ 1 0 18 1 \leq L \leq R \leq 10^{18} 1≤L≤R≤1018
思路:
还是和前几题差不多,参数设置为原数和数位和,然后原数由于过大显然无法直接搜,想想怎么去取模表示。
我们最理想的模数当然是把每次搜到最后得到的数字各个位数之和,但是我们发现在这个过程中这个数位和是发生变化的,所以我们就应该以一个定值作为模数,考虑枚举模数 [ 1 , l e n ∗ 9 ] [1,len*9] [1,len∗9] 这个范围即可
code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
int len=0,a[25],p;
ll L,R,dp[20][200][200];
ll dfs(int pos,int mod,int sum,bool limit){
if(!pos) return mod==0&&sum==p;
if(!limit&&~dp[pos][mod][sum]) return dp[pos][mod][sum];
int cur=limit?a[pos]:9;
ll res=0;
for(int i=0;i<=cur;i++){
res+=dfs(pos-1,(mod*10+i)%p,sum+i,limit&&(i==cur));
}
if(!limit) dp[pos][mod][sum]=res;
return res;
}
ll solve(ll x){
len=0;
while(x) a[++len]=x%10,x/=10;
ll res=0;
for(int i=1;i<=len*9;i++){
p=i;
memset(dp,-1,sizeof dp);
res+=dfs(len,0,0,1);
}
return res;
}
int main(){
scanf("%lld %lld",&L,&R);
printf("%lld\n",solve(R)-solve(L-1));
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
P3413 - SAC#1 - 萌数
题目描述:
定义存在长度至少为 2 2 2 的回文子串的数是萌数,求区间 l l l 到 r r r 内萌数的个数
1 ≤ n ≤ 1000 1 \leq n \leq 1000 1≤n≤1000,其中 n n n 代表 l , r l,r l,r 十进制下的位数
思路:
如果直接找萌数个数,那么只需要记录前两个数是什么,并且记录一个历史状态代表是否出现过子回文串即可。
code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int p=1e9+7;
const int maxn=1005;
string l,r;
int a[maxn];
int len=0;
ll dp[maxn][12][12][2];
ll dfs(int pos,int ppre,int pre,bool f,bool lead,bool limit){
if(!pos) return f;
if(!lead&&!limit&&~dp[pos][ppre+1][pre+1][f]) return dp[pos][ppre+1][pre+1][f];
int cur=limit?a[pos]:9;
ll res=0;
for(int i=0;i<=cur;i++){
if(lead&&!i) res=(res+dfs(pos-1,ppre,pre,f,lead,limit&&(i==cur)))%p;
else if(lead) res=(res+dfs(pos-1,pre,i,f,0,limit&&(i==cur)))%p;
else res=(res+dfs(pos-1,pre,i,f||(i==pre)||(i==ppre),0,limit&&(i==cur)))%p ;
}
if(!limit&&!lead) dp[pos][ppre+1][pre+1][f]=res;
return res;
}
bool check(string s){
for(int i=0;i<s.size();i++){
if(s[i]==s[i-1]&&i) return true;
if(i>=2&&s[i]==s[i-2]) return true;
}
return false;
}
ll solve(string s){
len=s.size();
for(int i=0;i<len;i++) a[len-i]=s[i]-'0';
return dfs(len,-1,-1,0,1,1);
}
int main(){
memset(dp,-1,sizeof dp);
cin>>l>>r;
int ans=0;
if(check(l)) ans=1;
printf("%lld\n",(solve(r)-solve(l)+ans+p)%p);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
!!
这里留了一个思考,就是我看到一篇题解前前一个数在数组里的参数可以省略,想了想好像是有点道理,因为当你解除 l i m i t limit limit 限制后取值都是 [ 0 , 9 ] [0,9] [0,9] 必然能取到回文,但是又没法去论证什么的,自己试了几种方法也不行。
看看有没有大佬能解决这个问题,或者等再过段时间看看自己的想法有没有变化