简单基础数位 dp 题
终于比较理解了数位 \(dp\) ( qwq
处理大数区间的计数,分成每一位考虑,\(f_{pos}\) 考虑从高到低位在第 \(pos\) 位并且满足某些条件的答案,这个东西我们可以记忆化搜索,但是注意要设计好状态,不然会漏或者重复计算某些情况 qwq 。
需要观察题目有没有前导 \(0\) 限制,不过这个也不是重点了。
关键在于对状态的设计。
数位 \(dp\) 模板题。
设当前我们要统计的数为 \(G\),\(dp_{pos,sta}\) 表示从高到低第 \(pos\) 位已经选了 \(sta\) 个 \(G\) 的方案数。直接 \(dp\) 即可。
int DFS(int pos,int sta,int lim,int zero)
{// lim 表示最高位限制,zero 表示是否在前导零
if(!pos) return sta;
if(!lim && !zero && (~dp[pos][sta])) return dp[pos][sta];
int sum=0, up=(lim)?s[pos]:9;
for(ri int i=0;i<=up;i++)
{
int pp=zero&&(!i);
sum+=DFS(pos-1,sta+((!pp)&&i==G),lim&&(i==up),zero&&(!i));//如果不在前导零才可以贡献,否则当统计 0 的贡献时候会炸掉。
}
if(!lim && !zero) dp[pos][sta]=sum;
return sum;
}
又一个数位 \(dp\) 模板题。
设 \(dp_{pos,pre}\) 为第 \(pos\) 位且上一位的数是 \(pre\) 时的答案,直接 \(dp\) 。注意当处于前导 \(0\) 状态却满足条件时可以直接 \(continue\)。
int DFS(int pos,int pre,int lim,int zero)
{
if(!pos) return 1;
if((!lim) && (!zero) && (~dp[pos][pre+20])) return dp[pos][pre+20];
int sum=0, up=(lim)?s[pos]:9;
for(ri int i=0;i<=up;i++)
{
if(abs(i-pre)<2&&(!zero)) continue;
sum+=DFS(pos-1,i,lim&&(i==up),zero&&(!i));
}
if(!lim && !zero) dp[pos][pre+20]=sum;
return sum;
}
比起前两道要难一些的数位 \(dp\) 题。
考虑一个字符串存在至少一个子串是回文串的情况。显然,这个子串的最短长度为 \(2\) 或 \(3\),所以我们可以考虑记录在第 \(pos\) 位时,前两位分别的值。然后你发现这连样例都过不去...
考虑一种情况是 \(090\),这种情况你发现 \(pos-2\) 位置上是处于前导 \(0\) 状态。所以我们还需要记录处于第 \(pos\) 位时,前两位是否处于前导 \(0\) 状态。然后发现这个东西要开 \(6\) 维数组...
\(dp_{pos,m1,m2,sta,qwq1,qwq2}\) 记录第 \(pos\) 位时,第 \(pos-1\) 位的数是 \(m1\),第 \(pos-2\) 位的数是 \(m2\),当前是否已经产生回文串 \(sta\),第 \(pos-1\) 位是否处于前导 \(0\) 状态 \(qwq1\),第 \(pos-2\) 位是否处于前导 \(0\) 状态 \(qwq2\) 。
int DFS(int pos,int sta,int m1,int m2,int qwq1,int qwq2,int lim,int zero)
{
if(!pos)
{
/*if(sta)
{
for(ri int i=len;i;i--) printf("%lld",cnt[i]);
puts("");
}*/
return sta;
}
if((!lim) && (!zero) && (~dp[pos][m1][m2][sta][qwq1][qwq2])) return dp[pos][m1][m2][sta][qwq1][qwq2];
int sum=0, up=(lim)?(s[pos]-'0'):9;
for(ri int i=0;i<=up;i++)
{
int qq=(((m1==i)&&(!qwq1))||((m2==i)&&(!qwq2)))||sta;
int pp=(zero&&(!i));
cnt[pos]=i;
sum=(sum+DFS(pos-1,qq,i,m1,pp,qwq1,lim&&(i==up),pp))%Mod;
cnt[pos]=0;
}
if((!lim) && (!zero) ) dp[pos][m1][m2][sta][qwq1][qwq2]=sum;
return sum;
}
带些思考的数位 \(dp\) 题。
考虑 \(1-9\) 区间的数的 \(lcm\) 最大为 \(2520\),所以你对于算出的那个数 \(x\),只需要考虑 \(x\%2520\) 即可。然后发现要开个差不多 \(20\times 2520 \times 2520\) 的数组还要用好多次,会 \(MLE\)...
观察到 \(1-9\) 区间的数的 \(lcm\) 个数仅有 \(48\) 个,可以用类似于离散化一样的思想预处理一下就好了。
inline void Init()
{
for(ri int a=0;a<=3;a++)
for(ri int b=0;b<=2;b++)
for(ri int c=0;c<=1;c++)
for(ri int d=0;d<=1;d++)
g[++cnt]=ksc(2,a)*ksc(3,b)*ksc(5,c)*ksc(7,d);
sort(g+1,g+1+cnt);
for(ri int i=1;i<=cnt;i++) book[g[i]]=i;
pw[0]=1;
for(ri int i=1;i<=18;i++) pw[i]=pw[i-1]*10;
}
int DFS(int pos,int sta,int now,int lim,int zero)
{
if(!pos)
{
if(sta%g[now]) return 0;
return 1;
}
if((!lim) && (!zero) && (~dp[pos][sta][now])) return dp[pos][sta][now];
int sum=0, up=(lim)?s[pos]:9;
for(ri int i=0;i<=up;i++)
{
int pp=(sta+i*pw[pos-1]%Mod)%Mod;
int qq=g[now];
if(i) qq=qq*i/__gcd(i,qq);
qq=book[qq];
sum+=DFS(pos-1,pp,qq,lim&&(i==up),zero&&(!i));
}
if((!lim) && (!zero)) dp[pos][sta][now]=sum;
return sum;
}
原数的值这个状态显然存不下...考虑到上一题我们的做法,将这个原数对某个数进行取模。
发现各位数字之和的值域 \(x\) 非常小,设原数为 \(sta\),则我们要得到 \(sta\%x==0\)。所以我们可以枚举各位数字之和 \(x\),把 \(x\) 作为模数即可。
int DFS(int pos,int sta,int now,int Mod,int lim,int zero)
{
if(!pos)
{
if((!sta) && now==Mod) return 1;
return 0;
}
if((!lim) && (!zero) && (~dp[pos][sta][now])) return dp[pos][sta][now];
int sum=0, up=(lim)?s[pos]:9;
for(ri int i=0;i<=up;i++) sum+=DFS(pos-1,(sta*10+i)%Mod,now+i,Mod,lim&&(i==up),zero&&(!i));
if((!lim) && (!zero)) dp[pos][sta][now]=sum;
return sum;
}
inline int S(int k)
{
int cnt=0;
while(k) s[++cnt]=k%10, k/=10;
int res=0;
for(ri int i=1;i<=cnt*9;i++)
{
memset(dp,-1,sizeof(dp));
res+=DFS(cnt,0,0,i,1,1);
}
return res;
}
考虑把原数先变为二进制表示。然后枚举二进制数的 \(1\) 的个数 \(i\),利用快速幂统计贡献。
注意在数位 \(dp\) 求方案数时不能对 \(1e7+7\) 取模,因为它不是质数。
int DFS(int pos,int sta,int now,int lim,int zero)
{
if(!pos) return (now==sta);
if((!lim) && (!zero) && (~dp[pos][sta])) return dp[pos][sta];
int sum=0, up=(lim)?s[pos]:1;
for(ri int i=0;i<=up;i++) sum=(sum+DFS(pos-1,sta+i,now,lim&&(i==up),zero&&(!i)));
if((!lim) && (!zero)) dp[pos][sta]=sum;
return sum;
}
inline int S(int k)
{
int cnt=0;
while(k) s[++cnt]=(k&1ll), k>>=1ll;
int res=1;
for(ri int i=1;i<=cnt;i++)
{
memset(dp,-1,sizeof(dp));
res=res*ksc(i,DFS(cnt,0,i,1,1))%Mod;
}
return res;
}
和之前题目一样的套路,直接判断第 \(i\) 位与 \(d\) 的关系即可。
int DFS(int pos,int sta,int lim,int zero)
{
if(!pos) return (!sta);
if((!lim) && (!zero) && (~dp[pos][sta])) return dp[pos][sta];
int sum=0, up=(lim)?(s[pos]-'0'):9;
for(ri int i=0;i<=up;i++)
{
if((len-pos+1)&1ll)
{
if(i==d) continue;
sum=(sum+DFS(pos-1,(sta*10+i)%m,lim&&(i==up),zero&&(!i)))%Mod;
}
else
{
if(i^d) continue;
sum=(sum+DFS(pos-1,(sta*10+i)%m,lim&&(i==up),zero&&(!i)))%Mod;
}
}
if((!lim) && (!zero)) dp[pos][sta]=sum;
return sum;
}
直接状压 \(dp\) 即可。但是我还是用了个数位 \(dp\),一开始我是用 \(dp_{sta,now}\) 表示当前顺序表示出来的数 \(\%\) \(m\) 的值,当前选的数的集合(用二进制状压表示)为 \(now\)。然后直接 \(TLE\)。。。
这种做法最后还要对 \(0-9\) 的每个数出现次数 \(cnt\) 进行去重。所以你发现在数位 \(dp\) 时会非常慢。考虑进行一个优化,对每个 \(0-9\) 的数在某位确定了之后,不再需要考虑相同的数编号互换的情况,我们可以把数位拆分后排序,这样不会影响答案并可以去掉重复操作。
inline int DFS(int pos,int sta,int now)
{
if(!pos) return (!sta);
if(~dp[sta][now]) return dp[sta][now];
int sum=0;
for(ri int i=1;i<=cnt;i++)
{
if(!now && !s[i]) continue;
if(!((1ll<<(i-1))&now) && (i==1 || s[i]!=s[i-1] || ((1ll<<(i-2))&now)))
sum+=DFS(pos-1,(sta*10+s[i])%m,now|(1ll<<(i-1)));
}
dp[sta][now]=sum;
return sum;
}
inline int S(int k)
{
while(k) s[++cnt]=k%10, k/=10;
sort(s+1,s+1+cnt);
memset(dp,-1,sizeof(dp));
return DFS(cnt,0,0);
}
直接暴力记录 \(dp_{pos,sta,m1,m2,t1,t2}\) 表示在第 \(pos\) 位,是否已经满足条件 \(sta\),\(pos+1\) 位的数 \(m1\) 和 \(pos+2\) 位的数 \(m2\),是否出现过 \(4\) 用 \(t1\) 记录,\(8\) 用 \(t2\) 记录。直接暴力数位 \(dp\) 即可。注意当数字总位数不为 \(11\) 时直接返回 \(0\),因为我们在数位 \(dp\) 时可以直接跳过前导 \(0\)。
int DFS(int pos,int sta,int m1,int m2,int t1,int t2,int lim)
{
if(!pos)
{
if(sta) return 1;
return 0;
}
if((!lim) && (~dp[pos][sta][m1][m2][t1][t2])) return dp[pos][sta][m1][m2][t1][t2];
int sum=0;
int dn=(pos==cnt)?1:0;
int up=(lim)?s[pos]:9;
for(ri int i=dn;i<=up;i++)
{
int f1,f2;
f1=t1|(i==4);
f2=t2|(i==8);
if(f1&&f2) continue;
int qwq=sta;
if(i==m1&&m1==m2) qwq=1;
sum+=DFS(pos-1,qwq,i,m1,f1,f2,lim&&(i==up));
}
if(!lim) dp[pos][sta][m1][m2][t1][t2]=sum;
return sum;
}
inline int S(int k)
{
cnt=0; memset(dp,-1,sizeof(dp));
while(k) s[++cnt]=k%10, k/=10;
if(cnt^11ll) return 0;
else return DFS(cnt,0,0,0,0,0,1);
}