shuweiDP
Posted hahamengbier
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了shuweiDP相关的知识,希望对你有一定的参考价值。
前言
大家有没有认真做几道题呢?(╮( ̄▽ ̄)╭)
没有做也没关系辣,好好听课没有问题的。
(登录hznoi)
简介
先 (copy) 一下论文:
在信息学竞赛中,有一类难度不大但异常麻烦的问题——数位计数问题,这类问题的主要特点是询问的答案和一段连续的数的各个数位相关,并且需要对时间效率有一定要求。由于解决这类问题往往意味着巨大的代码量,而众多的特殊情况又意味着出现错误的巨大可能性,因此很少有人愿意解决此类问题,但只要掌握好的方法,解决这类问题也并非想象中的那样困难。
在信息学竞赛中,有这样一类问题:求给定区间中,满足给定条件的某个 (D) 进制数或
此类数的数量。所求的限定条件往往与数位有关,例如数位之和、指定数码个数、数的大小
顺序分组等等。题目给定的区间往往很大,无法采用朴素的方法求解。此时,我们就需要利
用数位的性质,设计 (log(n))级别复杂度的算法。解决这类问题最基本的思想就是“逐位确定”
的方法。下面就让我们通过几道例题来具体了解一下这类问题及其思考方法。
数位 ( ext{DP}) 还是挺有特征的,一般都能一眼看出来这道题是不是数位 ( ext{DP}) ,所以关键的部分还在于具体实现方法,这里给出几种不同的方法:
1.递推
((1)) 优点:思路清晰,易于调试, for
循环很带感。
((2)) 缺点:码量看起来不太优秀,有时编程复杂度较大,有时需要很强的卡常能力,有时需要大力分类讨论,有的题不能用。
((3)) 关键:
- 枚举东西,转化问题;(有时不需要)
- 定义状态,写转移方程;
- 大力
for
循环,大力卡边界,大力分类讨论; - 卡不过就再大力;
- 还卡不过就用下一种方法 (downarrow) ;
2.记忆化搜索
((1)) 优点:码量少,编程复杂度较小,几乎所有题都能做。
((2)) 缺点:好像没什么缺点,但我就是不喜欢用,因为“ for
循环很带感”;
((3)) 关键:与递推一样,但一般不用卡常,也不用大力分类讨论。
3.写函数
大家自己看吧,我感觉一般不会有人用这个方法,毕竟 (3sim5) 个函数有些恐怖。
但这种解决问题的思想还是很重要的,下面会有题要用到。
例题
- #316. 花神的数论题:定义 (f[i][0/1][j]) 表示从高到低 ( ext{DP}) 到第 (i) 位,是否有限制,已经有 (j) 个 (1) 了的方案数,最后快速幂一下。
- #322. 0和1的熟练:定义 (f[i][0/1][j]) 表示从高到低 ( ext{DP}) 到第 (i) 位,是否有限制, (0) 的个数 (-) (1) 的个数为 (j) 的方案数,由于出了负数所以加个 (base) ,最后直接统计答案。
- #191. 有趣的数:有详细题解,关键思路是枚举数位和。
- #315. windy数:介绍一种处理前导 (0) 的方法:就是枚举有多少位,然后修改上限,多次 ( ext{DP}) 。
- #323. 苍与红的试炼:这个题只能用
记忆化搜索(Bfs) 的方式来做,因为是求最优解,而且没给上限。 - #343. 计数:用到了上面第三种方法的思路,有上限的一个个枚举,没有的直接算。
- #317. 手机号码:这道题做法比较多,主要展示几份代码:手机号码。
- #342. 数字计数:同样用到了上面第三种方法的思路,有上限的一个个枚举,没有的直接算。
- #321. haha数:直接定义 (f[i][0/1][a][b][c][d][j]) 表示从高到低 ( ext{DP}) 到第 (i) 位,是否有限制,有 (a) 个 (2) , (b) 个 (3) , (c) 个 (5) , (d) 个 (7) , (mod 2520=j) 的方案数,直接转移就行,记忆化搜索更好写。(但我喜欢写递推)
- #209. 自积:有详细题解,不是很难,关键是枚举 (2,3,5,7) 的个数,记忆化搜索更好写。(但我喜欢写递推)
- #324. F(x):有好几种方法:1.mengbier:从高位往低位算,高位的值可以传给低位,某一位的值超过 (18) 之后效果就都一样了,所以与 (18) 取个 (min) 后接着搜,定义 (f[i][0/1][j]) 表示从高到低 ( ext{DP}) 到第 (i) 位,是否有限制,第 (i) 位的 (F) 值为 (j) 的方案数;2.Jumbo;3.Asttinx64。
- #355. 方伯伯的商场之旅:预处理出六个数组,看代码讲吧。
- #356. 数数:用四个数组递推,看代码讲吧。
- #357. 魔法OIer小袁:Asttinx64。
虽然说可以直接看到,可怎么没人进来点个赞呢?(╮(╯﹏╰)╭)
Upd:呜呜呜,好像本来就没几个人看。
数位 ( ext{DP}) 大部分题都不难,大家先做几道题感受一下套路。
推荐做题顺序:(之前的两道题,做过的同学可以再看一眼)
- #316. 花神的数论题
- #322. 0和1的熟练
- #191. 有趣的数
- #340. B数
- #315. windy数
- #323. 苍与红的试炼
- #343. 计数
- #317. 手机号码
- #342. 数字计数
- HDU-3709
- #321. haha数
- #209. 自积
- #324. F(x)
- HDU-4352
这些题型都不太一样, (1sim9) 都是比较常规的题, (10sim14) 大都没有固定的套路,需要好好思考。
#191. 有趣的数
(Description)
定义函数 (f(n)) 表示 (n) 在十进制表示下的数字之和,求不超过 (N) 的所有正整数中有多少个数满足 (f(n)∣n) 。
(对于 100% 的数据, (1 leq N leq 10^{18}) 。)
(Solution)
看到数据范围,很容易可以排除时间复杂度为 (Theta(sqrt{n})) 及更高的算法,再仔细读题便不难想到使用数位 (DP) 来解决这道题。首先考虑枚举 (f) 的值为 (x) ,因为 (f(n) in [1,9,log_{10}n]) ,这时只需求出有多少数满足 (f(n)!==!x && n mod x!==!0) 即可。然后思考如何通过 (DP) 求解答案,我们定义 (g[i][0/1][j][k]) 表示从高位到低位枚举到了第 (i) 位、 (1) ~ (i!-!1) 位是否都跟上限 (N) 相同、当前数 (mod x!==!j) 、当前数 (f) 值为 (k) 的方案数,状态定义比较套路,都是根据我们所要求的东西直接定义出来的。然后就可以直接写出 (DP) 转移方程了,同时再把循环上界设得符合实际一些,便可以通过这个题。时间复杂度一般不用分析,即使它理论上无法 (AC) ,只要尽力去剪枝即可。
(Experience)
- 通过题意、数据范围和复杂度分析来判断题目为数位 (DP) ;
- 通过枚举某个限制来转化成 (DP) 求方案数;
- 按照限制设计状态并写出转移方程;
- 把循环上界设得符合实际一些来进行剪枝;
(Code)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long LL;
const int maxn=200+3;
int n;
int a[maxn];
int v[maxn];
LL g[maxn/10][2][maxn][maxn];
char ch;
LL HAHA(const int);
int main(){
while(ch=getchar(),ch<‘/‘);a[n=1]=ch-‘0‘;
while(ch=getchar(),ch>‘/‘) a[++n]=ch-‘0‘;
LL ans=0;
for(int i=1;i<=std::min(n*9,162);++i)
ans+=HAHA(i);
std::cout<<ans<<"
";
return 0;
}
inline LL HAHA(const int m){
register int i,j,k,l;LL x;
for(x=1,i=n;i;--i,x*=10)v[i]=x%m;
memset(g,0,sizeof(g));
g[0][1][0][0]=1;
for(i=1;i<=n;++i)
for(k=0;k<m;++k)
for(l=0;l<=(i-1)*9;++l){
if(g[i-1][0][k][l])
for(j=0;j<10;++j)
g[i][0][(k+v[i]*j)%m][l+j]+=g[i-1][0][k][l];
if(g[i-1][1][k][l]){
for(j=0;j<a[i];++j)
g[i][0][(k+v[i]*j)%m][l+j]+=g[i-1][1][k][l];
g[i][1][(k+v[i]*a[i])%m][l+a[i]]+=g[i-1][1][k][l];
}
}
return g[n][0][0][m]+g[n][1][0][m];
}
#209. 自积
(Description)
一个十进制正整数的数字积是指它各位数字的乘积,一个十进制正整数的自积是它的数字积再与它自身的乘积,求有多少个正整数的自积恰好在区间 ([L,R]) 中。
(Solution)
根据上题的经验,并通过思考,我们可以枚举 (2,3,5,7) 的个数来枚举数字积,并可以通过贪心检验来得到所有合法状态。再定义 (f[i][0/1][a][b][c][d]) 从高位到低位枚举到了第 (i) 位、 (1) ~ (i!-!1) 位是否都跟上限 (N) 相同、当前数中 (2) 有 (a) 个、 (3) 有 (b) 个、 (5) 有 (c) 个、 (7) 有 (d) 个的方案数,直接进行 (DP) 即可。
(Experience)
看了标程我就自闭了,好像数位 (DP) 用 (Dfs/Bfs) 有时候效果更好。
(Code)
1.标程:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#define SF scanf
#define PF printf
using namespace std;
typedef long long LL;
const int MAXN = 18;
const int MAXM = 30;
const LL MAX_VAL = 1000000000000000000LL;
LL dp[MAXN+10][32][20][15][12];
LL generate_max, ans, L, R;
int prime[] = { 2, 3, 5, 7 };
int t[4];
int add[10][4] = {
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ 1, 0, 0, 0 },
{ 0, 1, 0, 0 },
{ 2, 0, 0, 0 },
{ 0, 0, 1, 0 },
{ 1, 1, 0, 0 },
{ 0, 0, 0, 1 },
{ 3, 0, 0, 0 },
{ 0, 2, 0, 0 }
};
LL dfs(int cur, LL num, LL base, LL l, LL r) {
LL max_num = num + base - 1;
if(max_num < l || num > r) return 0;
if(cur == MAXN)
return !t[0] && !t[1] && !t[2] && !t[3];
if(l <= num && max_num <= r && ~dp[cur][t[0]][t[1]][t[2]][t[3]]) return dp[cur][t[0]][t[1]][t[2]][t[3]];
LL ret = 0;
base /= 10;
for(int i = num != 0; i <= 9; i++) {
bool ok = true;
for(int j = 0; j < 4; j++) ok = ok && add[i][j] <= t[j];
if(!ok) continue;
for(int j = 0; j < 4; j++) t[j] -= add[i][j];
ret += dfs(cur+1, num+i*base, base, l, r);
for(int j = 0; j < 4; j++) t[j] += add[i][j];
}
if(l <= num && max_num <= r) dp[cur][t[0]][t[1]][t[2]][t[3]] = ret;
return ret;
}
void generate(LL R, LL mul, int cur) {
if(mul > generate_max) return ;
if(cur > 3) {
ans += dfs(0, 0, MAX_VAL, (L-1) / mul + 1, R / mul);
return ;
}
generate(R, mul, cur+1);
t[cur]++;
generate(R, mul*prime[cur], cur);
t[cur]--;
}
int main() {
cin >> L >> R;
memset(dp, -1, sizeof(dp));
generate_max = sqrt(R) + 0.5;
generate(R, 1, 0);
cout << ans;
}
2.mengbier:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define min(x,y) (x<y?x:y)
typedef long long LL;
const int maxn=5e3+3;
const int A[]={0,0,1,0,2,0,1,0,3,0};
const int B[]={0,0,0,1,0,0,1,0,0,2};
const int C[]={0,0,0,0,0,1,0,0,0,0};
const int D[]={0,0,0,0,0,0,0,1,0,0};
struct Node{
LL M;
int a,b,c,d;
}s[maxn];
LL Max;
int tot;
int M[20];
LL f[20][2][29][19][11][10];
LL DP(Node);
LL HAHA(LL);
bool HA(int,int,int,int,LL);
signed main(){
LL L,R;std::cin>>L>>R;
std::cout<<HAHA(R)-HAHA(L-1)<<"
";
return 0;
}
inline LL HAHA(LL M){
if(!M)return 0;
tot=0,Max=M;
for(LL a=0,A=1;a<=28;++a,A*=2)
for(LL b=0,B=1;b<=18 && A*B<=Max;++b,B*=3)
for(LL c=0,C=1;c<=10 && A*B*C<=Max;++c,C*=5)
for(LL d=0,D=1;d<=9 && A*B*C*D<=Max;++d,D*=7)
if(HA(a,b,c,d,A*B*C*D))
s[++tot]=(Node){Max/(A*B*C*D),a,b,c,d};
LL ans=0;
for(int i=1;i<=tot;++i)
ans+=DP(s[i]);
return ans;
}
inline bool HA(int a,int b,int c,int d,LL M){
LL x=1,ans=0;M=Max/M;
while(b>1 && ans<=M)ans+=x*9,b-=2,x*=10;
while(a>2 && ans<=M)ans+=x*8,a-=3,x*=10;
while(d && ans<=M)ans+=x*7,--d,x*=10;
while(a && b && ans<=M)ans+=x*6,--a,--b,x*=10;
while(c && ans<=M)ans+=x*5,--c,x*=10;
while(a>1 && ans<=M)ans+=x*4,a-=2,x*=10;
while(b && ans<=M)ans+=x*3,--b,x*=10;
while(a && ans<=M)ans+=x*2,--a,x*=10;
return ans<=M;
}
inline LL DP(Node x){
int n=0;LL temp=x.M;
while(temp)
M[++n]=temp%10,temp/=10;
std::reverse(M+1,M+n+1);
register int i,j,a,b,c,d;
f[0][1][0][0][0][0]=1;
for(i=1;i<n;++i)
f[i][0][0][0][0][0]=1;
for(i=1;i<=n;++i)
for(a=min(x.a,3*(i-1));~a;--a)
for(b=min(x.b,2*(i-1-a/3));~b;--b)
for(c=min(x.c,i-1-a/3-b/2);~c;--c)
for(d=min(x.d,i-1-a/3-b/2-c);~d;--d){
if(f[i-1][0][a][b][c][d]){
const LL xx=f[i-1][0][a][b][c][d];
for(j=1;j<10;++j){
if(d+D[j]>x.d)continue;
if(c+C[j]>x.c)continue;
if(b+B[j]>x.b)continue;
if(a+A[j]>x.a)continue;
f[i][0][a+A[j]][b+B[j]][c+C[j]][d+D[j]]+=xx;
}
}
if(f[i-1][1][a][b][c][d] && M[i]){
const LL xx=f[i-1][1][a][b][c][d];
for(j=1;j<M[i];++j){
if(d+D[j]>x.d)continue;
if(c+C[j]>x.c)continue;
if(b+B[j]>x.b)continue;
if(a+A[j]>x.a)continue;
f[i][0][a+A[j]][b+B[j]][c+C[j]][d+D[j]]+=xx;
}
if(d+D[j]>x.d)continue;
if(c+C[j]>x.c)continue;
if(b+B[j]>x.b)continue;
if(a+A[j]>x.a)continue;
f[i][1][a+A[j]][b+B[j]][c+C[j]][d+D[j]]+=xx;
}
}
LL ans=f[n][0][x.a][x.b][x.c][x.d]+f[n][1][x.a][x.b][x.c][x.d];
for(i=1;i<=n;++i)
for(a=min(x.a,3*i);~a;--a)
for(b=min(x.b,2*(i-a/3));~b;--b)
for(c=min(x.c,i-a/3-b/2);~c;--c)
for(d=min(x.d,i-a/3-b/2-c);~d;--d)
f[i][0][a][b][c][d]=f[i][1][a][b][c][d]=0;
return ans;
}
以上是关于shuweiDP的主要内容,如果未能解决你的问题,请参考以下文章