标签:超过 continue base sizeof 例题 解决问题 因此 col 区间
大家有没有认真做几道题呢?(╮( ̄▽ ̄)╭)
没有做也没关系辣,好好听课没有问题的。
(登录hznoi)
先 \(copy\) 一下论文:
在信息学竞赛中,有一类难度不大但异常麻烦的问题——数位计数问题,这类问题的主要特点是询问的答案和一段连续的数的各个数位相关,并且需要对时间效率有一定要求。由于解决这类问题往往意味着巨大的代码量,而众多的特殊情况又意味着出现错误的巨大可能性,因此很少有人愿意解决此类问题,但只要掌握好的方法,解决这类问题也并非想象中的那样困难。
在信息学竞赛中,有这样一类问题:求给定区间中,满足给定条件的某个 \(D\) 进制数或
此类数的数量。所求的限定条件往往与数位有关,例如数位之和、指定数码个数、数的大小
顺序分组等等。题目给定的区间往往很大,无法采用朴素的方法求解。此时,我们就需要利
用数位的性质,设计 \(\log(n)\)级别复杂度的算法。解决这类问题最基本的思想就是“逐位确定”
的方法。下面就让我们通过几道例题来具体了解一下这类问题及其思考方法。
数位 \(\text{DP}\) 还是挺有特征的,一般都能一眼看出来这道题是不是数位 \(\text{DP}\) ,所以关键的部分还在于具体实现方法,这里给出几种不同的方法:
\((1)\) 优点:思路清晰,易于调试, for
循环很带感。
\((2)\) 缺点:码量看起来不太优秀,有时编程复杂度较大,有时需要很强的卡常能力,有时需要大力分类讨论,有的题不能用。
\((3)\) 关键:
for
循环,大力卡边界,大力分类讨论;\((1)\) 优点:码量少,编程复杂度较小,几乎所有题都能做。
\((2)\) 缺点:好像没什么缺点,但我就是不喜欢用,因为“ for
循环很带感”;
\((3)\) 关键:与递推一样,但一般不用卡常,也不用大力分类讨论。
大家自己看吧,我感觉一般不会有人用这个方法,毕竟 \(3\sim5\) 个函数有些恐怖。
但这种解决问题的思想还是很重要的,下面会有题要用到。
虽然说可以直接看到,可怎么没人进来点个赞呢?(╮(╯﹏╰)╭)
Upd:呜呜呜,好像本来就没几个人看。
数位 \(\text{DP}\) 大部分题都不难,大家先做几道题感受一下套路。
推荐做题顺序:(之前的两道题,做过的同学可以再看一眼)
定义函数 \(f(n)\) 表示 \(n\) 在十进制表示下的数字之和,求不超过 \(N\) 的所有正整数中有多少个数满足 \(f(n)∣n\) 。
(对于 100% 的数据, \(1 \leq N \leq 10^{18}\) 。)
看到数据范围,很容易可以排除时间复杂度为 \(\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\) ,只要尽力去剪枝即可。
#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<<"\n";
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];
}
一个十进制正整数的数字积是指它各位数字的乘积,一个十进制正整数的自积是它的数字积再与它自身的乘积,求有多少个正整数的自积恰好在区间 \([L,R]\) 中。
根据上题的经验,并通过思考,我们可以枚举 \(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\) 即可。
看了标程我就自闭了,好像数位 \(DP\) 用 \(Dfs/Bfs\) 有时候效果更好。
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)<<"\n";
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;
}
标签:超过 continue base sizeof 例题 解决问题 因此 col 区间
原文地址:https://www.cnblogs.com/hahamengbier/p/shuweiDP.html