把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷6400】[COI2008] UMNOZAK(数位DP水题)

点此看题面

  • 定义一个数的自积为这个数乘上它的所有数码,求自积在\([l,r]\)范围内的数的个数。
  • \(A\le B\le10^{18}\)

可能的数位积

考虑某一位数码上最多只可能有\(3\)\(2\)\(2\)\(3\)\(1\)\(2\)\(1\)\(3\)\(1\)\(5\)\(1\)\(7\)

因此我们可以直接枚举\(2,3,5,7\)各自的个数,求出至少需要多少位才可能存在这样一个数位积,那么至少需要位数超出\(18\)位的肯定直接不用管了。

剩余的可能的数位积个数实际上并不多,实测只有\(36100\)个。

数位\(DP\)

首先是数位\(DP\)的经典差分,用\([1,r]\)的答案减去\([1,l-1]\)的答案,那么就相当于只要求解\([1,n]\)形式的答案。

直接枚举数位积\(v_i\),然后就是要求\(\lfloor1,\lfloor\frac n{v_i}\rfloor]\)范围内有多少个数的数位积是\(v_i\)

先枚举从哪一位开始(因为可能有前导\(0\)),然后\(DP\)

\(f_{x,s,0/1}\)表示处理到第\(x\)位,剩余需要的数位积编号为\(s\),是否严格小于\(n\)的方案数。

转移时枚举当前位填的数,首先不能超出这一位的限制,其次必须是\(v_s\)的因数。

代码:\(O(\)能过\()\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define V 36100
#define LL long long
using namespace std;
LL l,r,v[V+5],d[V+5];int tot;map<LL,int> id;
int ct,a[20];I void Init(LL x) {ct=0;W(x) a[++ct]=x%10,x/=10;}
LL f[20][V+5];I LL DP(CI x,CI s,CI fg=0)
{
	if(!x) return v[s]==1;if(d[s]>x) return 0;if(fg&&~f[x][s]) return f[x][s];//DP完;位数不够;记忆化
	LL t=0;for(RI i=1,l=fg?9:a[x];i<=l;++i) !(v[s]%i)&&(t+=DP(x-1,id[v[s]/i],fg||(i^l)));//枚举当前位的数码
	return fg&&(f[x][s]=t),t;//记忆化
}
I LL Calc(Con LL& n)
{
	LL t=0;for(RI i=1,j;i<=tot;++i) for(Init(n/v[i]),j=ct;j>=d[i];--j) t+=DP(j,i,j!=ct);return t;//枚举数位积,枚举最高位
}
int main()
{
	RI i,j,k,p,t;for(i=0;i<=3*18;++i) for(j=0;j<=2*18;++j) for(k=0;k<=18;++k) for(p=0;p<=18;++p)//枚举2,3,5,7个数
		(t=i/3+(i%3>0)+j/2+(j%2>0)-(i%3==1&&j%2==1)+k+p)<=18&&(d[++tot]=t,id[v[tot]=(LL)pow(2,i)*pow(3,j)*pow(5,k)*pow(7,p)]=tot);//计算最少位数
	return memset(f,-1,sizeof(f)),scanf("%lld%lld",&l,&r),printf("%lld\n",Calc(r)-Calc(l-1)),0;//差分
}
posted @ 2021-05-25 17:50  TheLostWeak  阅读(82)  评论(0编辑  收藏  举报