P5842 [SCOI2012]Blinker 的仰慕者 题解
题意简述:
求 \([l,r]\) 内各个数位积为 \(k\) 的数的和。
解:
在看题解前,请先确认自己是否精通了数位 dp 模板题,否则会很难理解。
对于朴素的数位 dp,我们可以记录 \(f_{i,j}\) 代表枚举到第 \(x\) 位,还需要的乘积为 \(j\) 的数之和。
只有一个状态处理数的个数,但本题要求数的和,这样我们比较难用一位去处理。
所以再定义 \(g_{i,j}\) 为枚举到第 \(x\) 位,还需要的乘积为 \(j\) 的数个数。
那么合并上来即是:
\(g_{i,j}=\sum g_{i-1,j/k}\)
\(f_{i,j}=\sum f_{i-1,j/k}+k\times g_{i-1,j/k}\times 10^{i-1}\)
那么直接枚举向下搜索即可,如果搜到底部 \(j=1\),那么所有应该除的数已除完,直接返回和为 \(0\),个数为 \(1\),否则返回个数为 \(0\)。
特殊处理一下 \(K=0\)。
当 \(K=0\) 时就记录状态为当前数是否有 \(0\) 这一位,然后同样记录 \(f,g\) 两个数组的值,往下搜即可。
用 map 存储记忆化来解决空间问题,期望得分 \(20pts\)。(不愧是黑,部分分都那么难拿)。
考虑朴素 dp 的缺陷。
1、调用 map 开销大量时间。
2、有许多冗余状态。
说白了就是数存多了,所以我们对此进行优化。
由于数位只有 \(0\) 到 \(9\),那么质数只有 \(2,3,5,7\),所以对我们来说有用的数仅仅只有 \(2^a\times3^b\times5^c\times7^d\)。
那么我们可以把状态压成 \(f_{i,a,b,c,d}\) 代表枚举到第 \(i\) 位我们还剩下的因子为 \(2^a\times3^b\times5^c\times7^d\) 的和,\(g_{i,a,b,c,d}\) 同理。
转移比较容易,直接预处理出来每个数包含哪些质因子,在对应的质因子上减去即可。
期望得分:\(50pts\),实际得分:\(20pts\),被卡空间了。
#include<iostream>
#include<cstdio>
#include<cstring>
#define int long long
using namespace std;
const int mod=20120427;
int n,cnt,a[20],f[20][55][37][20][20],g[20][55][37][20][20],t2,t3,t5,t7,power[20],k,f2[20][2][2][2],g2[20][2][2][2];
int s[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}
};
pair<int,int>dfs(int x,int k2,int k3,int k5,int k7,bool flag,bool zero,bool zero2)
{
//cout<<x<<" "<<k2<<" "<<k3<<" "<<k5<<" "<<k7<<" "<<flag<<" "<<zero<<" "<<f[x][k2][k3][k5][k7]<<endl;
if(x==0&&zero2)return {0,1};
if(x==0&&!k2&&!k3&&!k5&&!k7)return {0,1};
if(x==0)return {0,0};
if(!flag&&!zero&&f[x][k2][k3][k5][k7]!=-1)return {f[x][k2][k3][k5][k7],g[x][k2][k3][k5][k7]};
int ed=9;
if(flag)ed=a[x];
int sum=0,tot=0;
for(int i=0;i<=ed;i++)
{
if(!zero&&i==0&&k!=0)continue;
int p2=k2-s[i][0],p3=k3-s[i][1],p5=k5-s[i][2],p7=k7-s[i][3];
if((p2<0||p3<0||p5<0||p7<0))continue;
pair<int,int>res=dfs(x-1,p2,p3,p5,p7,flag&(i==a[x]),zero&(i==0),zero2|((!zero)&(!i)));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
if(!flag&&!zero)f[x][k2][k3][k5][k7]=sum,g[x][k2][k3][k5][k7]=tot;
return {sum,tot};
}
pair<int,int>dfs2(int x,bool flag,bool zero,bool zero2)
{
if(x==0)return {0,zero2};
if(f2[x][flag][zero][zero2]!=-1)return {f2[x][flag][zero][zero2],g2[x][flag][zero][zero2]};
int ed=9;
if(flag)ed=a[x];
int tot=0,sum=0;
for(int i=0;i<=ed;i++)
{
pair<int,int>res=dfs2(x-1,flag&(i==a[x]),zero&(i==0),zero2|((!zero)&(!i)));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
f2[x][flag][zero][zero2]=sum,g2[x][flag][zero][zero2]=tot;
return {sum,tot};
}
int solve(int x)
{
cnt=0;
while(x)
{
a[++cnt]=x%10;
x/=10;
}
if(k==0)
{
memset(f2,-1,sizeof(f2));
memset(g2,0,sizeof(g2));
int res=dfs2(cnt,1,1,0).first;
return res;
}
int t=k;
t2=t3=t5=t7=0;
while(t%2==0)t/=2,t2++;
while(t%3==0)t/=3,t3++;
while(t%5==0)t/=5,t5++;
while(t%7==0)t/=7,t7++;
//if(t2>3*cnt||t3>2*cnt||t5>cnt||t7>cnt)return 0;
if(k!=0&&t!=1)return 0;
for(int i=0;i<=cnt;i++)
for(int j=0;j<=t2;j++)
for(int sp=0;sp<=t3;sp++)
for(int q=0;q<=t5;q++)
for(int l=0;l<=t7;l++)f[i][j][sp][q][l]=-1,g[i][j][sp][q][l]=0;
int res=dfs(cnt,t2,t3,t5,t7,1,1,0).first;
return res;
}
signed main()
{
// freopen("fans.in","r",stdin);
// freopen("fans.out","w",stdout);
scanf("%lld",&n);
power[0]=1;
for(int i=1;i<=18;i++)power[i]=power[i-1]*10,power[i]%=mod;
for(int i=1;i<=n;i++)
{
int l,r;
scanf("%lld%lld%lld",&l,&r,&k);
int ans=solve(r)-solve(l-1);
ans=(ans%mod+mod)%mod;
printf("%lld\n",ans);
}
return 0;
}
考虑上述方法缺陷:有特别多状态是用不到的,如 \(2^{54}\times 3^{36}\),而我们把它们都压入了数组。
解决上述问题:直接预处理出来 \(10^{18}\) 以内满足 \(2^a\times3^b\times5^c\times7^d\) 的数,直接循环枚举就好。
那么现在我们转移状态时要知道下一个状态的编号为多少,这个提前用二分预处理出来即可,注意清空数组时得用栈先存储哪些状态发生了变化,再清空。
期望得分:\(100pts\),实际得分:\(50pts\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define int long long
using namespace std;
const int mod=20120427;
const int N=7e4+5;
const int M=1e18+5;
int n,cnt,a[20],f[20][N],g[20][N],t2,t3,t5,t7,power[20],f2[20][2][2][2],g2[20][2][2][2],num[N],lent,to[N][10],cnp;
int s[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}
};
pair<int,int>q[N];
pair<int,int>dfs(int x,int k,bool flag,bool zero)
{
if(k==0)return {0,0};
//cout<<x<<" "<<k<<" "<<flag<<" "<<zero<<endl;
if(x==0&&k==1)return {0,1};
if(x==0)return {0,0};
if(!flag&&!zero&&f[x][k]!=-1)return {f[x][k],g[x][k]};
int ed=9;
if(flag)ed=a[x];
int sum=0,tot=0;
for(int i=0;i<=ed;i++)
{
if(!zero&&i==0)continue;
if(!to[k][i])continue;
pair<int,int>res=dfs(x-1,to[k][i],flag&(i==a[x]),zero&(i==0));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
if(!flag&&!zero)f[x][k]=sum,g[x][k]=tot,q[++cnp]={x,k};
return {sum,tot};
}
pair<int,int>dfs2(int x,bool flag,bool zero,bool zero2)
{
if(x==0)return {0,zero2};
if(f2[x][flag][zero][zero2]!=-1)return {f2[x][flag][zero][zero2],g2[x][flag][zero][zero2]};
int ed=9;
if(flag)ed=a[x];
int tot=0,sum=0;
for(int i=0;i<=ed;i++)
{
pair<int,int>res=dfs2(x-1,flag&(i==a[x]),zero&(i==0),zero2|((!zero)&(!i)));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
f2[x][flag][zero][zero2]=sum,g2[x][flag][zero][zero2]=tot;
return {sum,tot};
}
int numfind(int x)
{
int l=1,r=lent;
while(l<r)
{
int mid=(l+r)>>1;
if(num[mid]<x)l=mid+1;
else r=mid;
}
return l;
}
int solve(int x,int k)
{
cnt=0;
while(x)
{
a[++cnt]=x%10;
x/=10;
}
if(k==0)
{
memset(f2,-1,sizeof(f2));
memset(g2,0,sizeof(g2));
int res=dfs2(cnt,1,1,0).first;
return res;
}
int t=k;
t2=t3=t5=t7=0;
while(t%2==0)t/=2,t2++;
while(t%3==0)t/=3,t3++;
while(t%5==0)t/=5,t5++;
while(t%7==0)t/=7,t7++;
if(t!=1)return 0;
cnp=0;
int res=dfs(cnt,numfind(k),1,1).first;
for(int i=cnp;i>0;i--)f[q[i].first][q[i].second]=-1,g[q[i].first][q[i].second]=0;
return res;
}
void prepare()
{
int num2=1;
for(int i=0;i<=54;i++)
{
if(num2>M)break;
int num3=1;
for(int j=0;j<=36;j++)
{
if(num2*num3>M)break;
int num5=1;
for(int q=0;q<=18;q++)
{
if(num2*num3*num5>M)break;
int num7=1;
for(int l=0;l<=18;l++)
{
if(num2*num3*num5*num7>M)break;
num[++lent]=num2*num3*num5*num7;
num7*=7;
}
num5*=5;
}
num3*=3;
}
num2*=2;
}
sort(num+1,num+1+lent);
for(int i=1;i<=lent;i++)
{
to[i][0]=to[i][1]=i;
for(int j=2;j<=9;j++)
{
if(num[i]%j)continue;
to[i][j]=numfind(num[i]/j);
}
}
}
int read()
{
char ch=getchar();
int sum=0;
while(ch>'9'||ch<'0')ch=getchar();
while(ch>='0'&&ch<='9')sum=(sum<<1)+(sum<<3)+(ch^48),ch=getchar();
return sum;
}
void write(int x)
{
if(x/10)write(x/10);
putchar((x%10)^48);
}
signed main()
{
//freopen("fans.in","r",stdin);
//freopen("fans.out","w",stdout);
prepare();
n=read();
power[0]=1;
memset(f,-1,sizeof(f));
for(int i=1;i<=18;i++)power[i]=power[i-1]*10,power[i]%=mod;
for(int i=1;i<=n;i++)
{
int l,r,k;
l=read(),r=read(),k=read();
int ans=solve(r,k)-solve(l-1,k);
ans=(ans%mod+mod)%mod;
write(ans);
putchar('\n');
}
return 0;
}
那么剩下的也不好弄了,直接上剪枝。
记录一下每个状态搜到 \(1\) 的最小步数,如果现在状态的步数已经大于了剩下的位数,说明无法达到,直接返回。
记录最小步数就从自己可能的下一个状态转移上来,取最小值。
期望得分:\(100pts\),实际得分:\(100pts\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define int long long
using namespace std;
const int mod=20120427;
const int N=7e4+5;
const int M=1e18+5;
int n,cnt,a[20],f[20][N],g[20][N],t2,t3,t5,t7,power[20],f2[20][2][2][2],g2[20][2][2][2],num[N],lent,to[N][10],cnp,dep[N];
pair<int,int>q[N];
pair<int,int>dfs(int x,int k,bool flag,bool zero)
{
if(k==0)return {0,0};
if(dep[k]>x)return {0,0};
//cout<<x<<" "<<k<<" "<<flag<<" "<<zero<<endl;
if(x==0&&k==1)return {0,1};
if(x==0)return {0,0};
if(!flag&&!zero&&f[x][k]!=-1)return {f[x][k],g[x][k]};
int ed=9;
if(flag)ed=a[x];
int sum=0,tot=0;
for(int i=0;i<=ed;i++)
{
if(!zero&&i==0)continue;
if(!to[k][i])continue;
pair<int,int>res=dfs(x-1,to[k][i],flag&(i==a[x]),zero&(i==0));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
if(!flag&&!zero)f[x][k]=sum,g[x][k]=tot,q[++cnp]={x,k};
return {sum,tot};
}
pair<int,int>dfs2(int x,bool flag,bool zero,bool zero2)
{
if(x==0)return {0,zero2};
if(f2[x][flag][zero][zero2]!=-1)return {f2[x][flag][zero][zero2],g2[x][flag][zero][zero2]};
int ed=9;
if(flag)ed=a[x];
int tot=0,sum=0;
for(int i=0;i<=ed;i++)
{
pair<int,int>res=dfs2(x-1,flag&(i==a[x]),zero&(i==0),zero2|((!zero)&(!i)));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
f2[x][flag][zero][zero2]=sum,g2[x][flag][zero][zero2]=tot;
return {sum,tot};
}
int numfind(int x)
{
int l=1,r=lent;
while(l<r)
{
int mid=(l+r)>>1;
if(num[mid]<x)l=mid+1;
else r=mid;
}
return l;
}
int solve(int x,int k)
{
cnt=0;
while(x)
{
a[++cnt]=x%10;
x/=10;
}
if(k==0)
{
memset(f2,-1,sizeof(f2));
memset(g2,0,sizeof(g2));
int res=dfs2(cnt,1,1,0).first;
return res;
}
int t=k;
t2=t3=t5=t7=0;
while(t%2==0)t/=2,t2++;
while(t%3==0)t/=3,t3++;
while(t%5==0)t/=5,t5++;
while(t%7==0)t/=7,t7++;
if(t!=1)return 0;
cnp=0;
int res=dfs(cnt,numfind(k),1,1).first;
for(int i=cnp;i>0;i--)f[q[i].first][q[i].second]=-1,g[q[i].first][q[i].second]=0;
return res;
}
void prepare()
{
int num2=1;
for(int i=0;i<=54;i++)
{
if(num2>M)break;
int num3=1;
for(int j=0;j<=36;j++)
{
if(num2*num3>M)break;
int num5=1;
for(int q=0;q<=18;q++)
{
if(num2*num3*num5>M)break;
int num7=1;
for(int l=0;l<=18;l++)
{
if(num2*num3*num5*num7>M)break;
num[++lent]=num2*num3*num5*num7;
num7*=7;
}
num5*=5;
}
num3*=3;
}
num2*=2;
}
sort(num+1,num+1+lent);
for(int i=1;i<=lent;i++)
{
if(i!=1)dep[i]=1e18;
to[i][0]=to[i][1]=i;
for(int j=2;j<=9;j++)
{
if(num[i]%j)continue;
int lim=numfind(num[i]/j);
to[i][j]=lim;
dep[i]=min(dep[i],dep[lim]+1);
}
}
}
int read()
{
char ch=getchar();
int sum=0;
while(ch>'9'||ch<'0')ch=getchar();
while(ch>='0'&&ch<='9')sum=(sum<<1)+(sum<<3)+(ch^48),ch=getchar();
return sum;
}
void write(int x)
{
if(x/10)write(x/10);
putchar((x%10)^48);
}
signed main()
{
//freopen("fans.in","r",stdin);
//freopen("fans.out","w",stdout);
prepare();
n=read();
power[0]=1;
memset(f,-1,sizeof(f));
for(int i=1;i<=18;i++)power[i]=power[i-1]*10,power[i]%=mod;
for(int i=1;i<=n;i++)
{
int l,r,k;
l=read(),r=read(),k=read();
int ans=solve(r,k)-solve(l-1,k);
ans=(ans%mod+mod)%mod;
write(ans);
putchar('\n');
}
return 0;
}

浙公网安备 33010602011771号