shuweiDP

Posted hahamengbier

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了shuweiDP相关的知识,希望对你有一定的参考价值。

前言

大家有没有认真做几道题呢?(╮( ̄▽ ̄)╭)
没有做也没关系辣,好好听课没有问题的。
(登录hznoi)

简介

(copy) 一下论文:

在信息学竞赛中,有一类难度不大但异常麻烦的问题——数位计数问题,这类问题的主要特点是询问的答案和一段连续的数的各个数位相关,并且需要对时间效率有一定要求。由于解决这类问题往往意味着巨大的代码量,而众多的特殊情况又意味着出现错误的巨大可能性,因此很少有人愿意解决此类问题,但只要掌握好的方法,解决这类问题也并非想象中的那样困难。

在信息学竞赛中,有这样一类问题:求给定区间中,满足给定条件的某个 (D) 进制数或
此类数的数量。所求的限定条件往往与数位有关,例如数位之和、指定数码个数、数的大小
顺序分组等等。题目给定的区间往往很大,无法采用朴素的方法求解。此时,我们就需要利
用数位的性质,设计 (log(n))级别复杂度的算法。解决这类问题最基本的思想就是“逐位确定”
的方法。下面就让我们通过几道例题来具体了解一下这类问题及其思考方法。

数位 ( ext{DP}) 还是挺有特征的,一般都能一眼看出来这道题是不是数位 ( ext{DP}) ,所以关键的部分还在于具体实现方法,这里给出几种不同的方法:

1.递推

((1)) 优点:思路清晰,易于调试, for 循环很带感。

((2)) 缺点:码量看起来不太优秀,有时编程复杂度较大,有时需要很强的卡常能力,有时需要大力分类讨论,有的题不能用

((3)) 关键:

  1. 枚举东西,转化问题;(有时不需要)
  2. 定义状态,写转移方程;
  3. 大力 for 循环,大力卡边界,大力分类讨论;
  4. 卡不过就再大力;
  5. 还卡不过就用下一种方法 (downarrow)

2.记忆化搜索

((1)) 优点:码量少,编程复杂度较小,几乎所有题都能做。

((2)) 缺点:好像没什么缺点,但我就是不喜欢用,因为“ for 循环很带感”;

((3)) 关键:与递推一样,但一般不用卡常,也不用大力分类讨论。

3.写函数

2009年国家集训队论文(高逸涵)

大家自己看吧,我感觉一般不会有人用这个方法,毕竟 (3sim5) 个函数有些恐怖。

但这种解决问题的思想还是很重要的,下面会有题要用到。

例题

  1. #316. 花神的数论题:定义 (f[i][0/1][j]) 表示从高到低 ( ext{DP}) 到第 (i) 位,是否有限制,已经有 (j)(1) 了的方案数,最后快速幂一下。
  2. #322. 0和1的熟练:定义 (f[i][0/1][j]) 表示从高到低 ( ext{DP}) 到第 (i) 位,是否有限制, (0) 的个数 (-) (1) 的个数为 (j) 的方案数,由于出了负数所以加个 (base) ,最后直接统计答案。
  3. #191. 有趣的数:有详细题解,关键思路是枚举数位和。
  4. #315. windy数:介绍一种处理前导 (0) 的方法:就是枚举有多少位,然后修改上限,多次 ( ext{DP})
  5. #323. 苍与红的试炼:这个题只能用记忆化搜索 (Bfs) 的方式来做,因为是求最优解,而且没给上限。
  6. #343. 计数:用到了上面第三种方法的思路,有上限的一个个枚举,没有的直接算。
  7. #317. 手机号码:这道题做法比较多,主要展示几份代码:手机号码
  8. #342. 数字计数:同样用到了上面第三种方法的思路,有上限的一个个枚举,没有的直接算。
  9. #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) 的方案数,直接转移就行,记忆化搜索更好写。(但我喜欢写递推)
  10. #209. 自积:有详细题解,不是很难,关键是枚举 (2,3,5,7) 的个数,记忆化搜索更好写。(但我喜欢写递推)
  11. #324. F(x):有好几种方法:1.mengbier:从高位往低位算,高位的值可以传给低位,某一位的值超过 (18) 之后效果就都一样了,所以与 (18) 取个 (min) 后接着搜,定义 (f[i][0/1][j]) 表示从高到低 ( ext{DP}) 到第 (i) 位,是否有限制,第 (i) 位的 (F) 值为 (j) 的方案数;2.Jumbo;3.Asttinx64
  12. #355. 方伯伯的商场之旅:预处理出六个数组,看代码讲吧。
  13. #356. 数数:用四个数组递推,看代码讲吧。
  14. #357. 魔法OIer小袁Asttinx64

虽然说可以直接看到,可怎么没人进来点个赞呢?(╮(╯﹏╰)╭)
Upd:呜呜呜,好像本来就没几个人看。
数位 ( ext{DP}) 大部分题都不难,大家先做几道题感受一下套路。
推荐做题顺序:(之前的两道题,做过的同学可以再看一眼)

  1. #316. 花神的数论题
  2. #322. 0和1的熟练
  3. #191. 有趣的数
  4. #340. B数
  5. #315. windy数
  6. #323. 苍与红的试炼
  7. #343. 计数
  8. #317. 手机号码
  9. #342. 数字计数
  10. HDU-3709
  11. #321. haha数
  12. #209. 自积
  13. #324. F(x)
  14. 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)

  1. 通过题意、数据范围和复杂度分析来判断题目为数位 (DP)
  2. 通过枚举某个限制来转化成 (DP) 求方案数;
  3. 按照限制设计状态并写出转移方程;
  4. 把循环上界设得符合实际一些来进行剪枝;

(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的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——CSS选择器

谷歌浏览器调试jsp 引入代码片段,如何调试代码片段中的js

片段和活动之间的核心区别是啥?哪些代码可以写成片段?

VSCode自定义代码片段——.vue文件的模板

VSCode自定义代码片段6——CSS选择器

VSCode自定义代码片段——声明函数