1 条题解
-
0
自动搬运
来自洛谷,原作者为

FFTotoro
龙猫搬运于
2025-08-24 22:50:20,当前版本为作者最后更新于2024-08-23 19:16:20,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
模拟赛 T3,场切了。赛后得知原题后翻了一下题解区,发现居然没有和我一样的做法,于是写一篇题解造福后人。
下文记整数 的“长度”(即十进制下的位数)为 。
考虑数位 DP,从高位到低位枚举。记录三个信息,分别为当前处理到的位置 (这里规定下标从 开始,即最高位所对应的 ,次高位的 ,以此类推)、目前确定的所有位是不是全部为 (即处于“前导零”的阶段),以及后面的位是否可以随便取(例如上限为 ,目前确定的最高的两位是 ,那么后面两位就可以取任意数)。使用 dfs 的写法,在 dfs 的同时动态维护一个数组 ,表示每个数字在前面分别出现了多少次。
首先特判边界情况,对于已经确定所有位的情况(即当前的 ,前面的 位全部确定完了),直接暴力算出当前的值并加上。
对于后面不能任意取的情况,直接按照常规数位 DP 方法继续递归下去就行了;对于后面可以可以任意取的情况,此时需要一点组合数学方面的知识。枚举最终出现次数最多的最大数 以及其出现次数 ,接着处理出其他所有数字 最多新增的出现次数(即不考虑之前确定的 次),就变成了以下的问题:有若干种数字,数字 最多取 个,要求取正好 个数字(后面总共要填 个数字,除去 则总共要填 个),问有多少种不同的数字排列( 和 算不同的选法,即可重集排列)。
想要求解上面这个问题,首先在所有数字 选的个数 都确定的情况下,根据可重集排列数公式,答案就是 $\dfrac{(\mathrm{len}(n)-x)!}{\prod\limits_{d=0}^9w_d!}$(这里对于 有 ,否则有 )。回到原问题,在给定 取值范围的情况下,这个问题可以 DP 求解:设 表示考虑了前 种数字,已经选了 个数字的答案(这里先不乘上 ),有转移 ,这里的 需要你在可行的范围内暴力枚举。最后记得将答案乘上 ;你也可以为了方便处理,把需要选 个的数字 单独处理(这样对于其他 只需要存储一个 的上界,即 ),即在 DP 过程中不考虑 的情况,只是最后需要将答案乘上 而不是 。
这道题似乎就做完了。但是有个很恶心的点,就是前导 。上面的计算方法可能会导致把带有前导 的数的答案算错(即可能把 算成 ,因为在考虑前导 的情况下 比 多,然而事实上 )。对于这种情况,只要考虑如果确定的所有位都是 ,即使当前这一位和后面的数字是任意取,也要枚举清楚这一位到底要取哪一个数字,然后再递归下去,用上面的方法求解即可。
实测极限数据不超过 ,可以通过此题。还是没搞清楚上面是怎么做的,可以参考代码注释。
放代码:
#include<bits/stdc++.h> #define int long long using namespace std; const int p=1e9+7; static int f[52],d[52]; int n,r,c[10]; string s; inline void chadd(int &x,int y){ if((x+=y)>=p)x-=p; } inline int qpow(int a,int b){ int r=1; while(b){ if(b&1)(r*=a)%=p; (a*=a)%=p,b>>=1; } return r; } inline int bf(){ int x=0,d=0; for(int i=0;i<10;i++) if(c[i]>=x)x=c[i],d=i; return d; } // 暴力求当前的值 inline int get(vector<int> &c,int s){ if(s<0)return 0; vector<int> f(s+1); f[0]=1; for(int i:c)if(i){ vector<int> g(s+1); for(int j=0;j<=s;j++) if(f[j])for(int k=0;k<=i&&j+k<=s;k++) chadd(g[j+k],f[j]*d[k]%p); f=g; } // 进行上述的 DP 转移,c[i] 是数字 i 取的个数上限 return f[s]; } void dfs(int x,bool l,bool b){ // 分别表示:当前位置,目前确定的是否全是 0,从这一位开始能不能随便取 if(x==n)return chadd(r,l?0:bf()); // 暴力求 if(!b){ if(l){ for(int i=0;i<10;i++){ if(i)c[i]++; dfs(x+1,!i,false); if(i)c[i]--; } // 有前导 0,继续递归下去 } else{ for(int i=1;i<10;i++) for(int j=max(c[i],1ll);j<=c[i]+n-x;j++){ vector<int> w; for(int k=0;k<i;k++) w.emplace_back(j-c[k]); for(int k=i+1;k<10;k++) w.emplace_back(j-c[k]-1); // 求每个数字取的个数的上限 bool b=true; for(int i:w)if(i<0){b=false; break;} if(b)chadd(r,f[n-x]*d[j-c[i]]%p*get(w,n-x-j+c[i])%p*i%p); } // 枚举 i,j 进行求解 } return; } for(int i=s[x]-48;~i;i--){ if(i||!l)c[i]++; dfs(x+1,l&&!i,i==s[x]-48); if(i||!l)c[i]--; } // 常规的数位 DP } main(){ ios::sync_with_stdio(false); for(int i=f[0]=d[0]=1;i<=51;i++) d[i]=qpow(f[i]=f[i-1]*i%p,p-2); // 预处理阶乘及其逆元 int t; cin>>t; while(t--){ cin>>s,n=s.length(),r=0; memset(c,0,sizeof(c)); dfs(0,true,true),cout<<r<<'\n'; } return 0; }
- 1
信息
- ID
- 9187
- 时间
- 3000ms
- 内存
- 256MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者