P9640 [SNCPC2019] Digit Mode
很不常规的数位 DP。
下文记:
- \(n\) 为原数,\(num\) 为实际填写的数。
- \(m(num)\) 为 \(num\) 各数位的众数。
- \(c_i\) 为搜索过程中数码 \(i\) 填写的次数。
- \(n\) 的最高位为 \(len\),最低位为 \(1\)。
在 DFS 过程中计算 \(m(num)\),需要的上下文信息太多(比如整个 \(c\) 数组),状态数太大。
一个常用的技巧是“提前钦定”,即枚举 \(m(num)\) 的值 \(x\),该众数的出现次数 \(y\),看有多少个 \(num\) 满足条件(比如 P4127 同类分布 - 题解)。
在主函数中钦定,我们发现仍不好做。原因在于有前导零和当前位取值范围的限制。
既然如此,我们不妨挪到搜索过程中,在每个“既不是前导零,也没有取值范围限制”的数位上钦定。
具体来说,当前在填写数位 \(p\),若 !lim&&!zro,我们即可枚举众数 \(x\),再枚举其出现次数 \(y(\max\{1,c_x\}\le y\le )\)。
由于没有 lim 和 zro 的限制,所以每个数码的最多填写次数是固定的,更具体些:
接下来我们要做的,是填充剩下的 \(p\) 位。
- \(x\) 的次数已经固定为 \(y\),所以要占据 \(p\) 位中的 \(y-c_x\) 位。
- 其他数的次数不固定,但必须 \(\le limit_i\)。
第二条可以用类似多重背包的方式进行转移。记 \(f_{i,j}\) 为前 \(i\) 个数码,共占据了 \(j\) 个位置的方案数。则有转移(\(x\) 不参与,\(limit\) 只有 \(9\) 个位置):
最终对答案的贡献为:
下面是一个无关紧要的优化:
注意到一些同构,可令 \(F_{i,j}=\dfrac{f_{i,j}}{j!}\),则转移为:
\[F_{i,j}=\sum_{k=0}^{limit_i} \frac{F_{i-1,j-k}}{k!} \]最终对答案的贡献为:
\[F_{9,p-(y-c_x)}\times \frac{p!}{(y-c_x)!} \]这样来做,码量和常数都会小一些。
至于不满足 !lim&&!zro,就按正常的数位 DP 往下搜即可。
如果搜到了末尾,就暴力判定。
时间复杂度 \(O(B^4 N^3)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
using namespace std;
const int N=55,P=1e9+7;
int t,a[N],len,c[10],fac[N],inv[N];
string s;
inline int qp(int x,int n){int a=1;while(n){if(n&1) a=a*x%P;x=x*x%P,n>>=1;}return a;
}
inline int bf(){int x=0,p=0;for(int i=9;~i;i--) if(c[i]>x) x=c[i],p=i;return p;
}
inline int calc(vector<int>& v,int s){//求方案数if(s<0) return 0;vector<int> f(s+1),g(s+1);f[0]=1;for(int i:v){g.clear();g.resize(s+1);for(int j=0;j<=s;j++){for(int k=0;k<=i&&j+k<=s;k++){(g[j+k]+=f[j]*inv[k])%=P;}}f=g; }return f[s];
}
inline int dfs(int p,bool lim,bool zro){if(!p) return bf();int ans=0;if(!lim&&!zro){//枚举众数x及其出现次数yfor(int x=1;x<10;x++){for(int y=max(1ll,c[x]);y<=c[x]+p;y++){vector<int> w;for(int k=0;k<x;k++) w.eb(y-c[k]);for(int k=x+1;k<10;k++) w.eb(y-c[k]-1);bool flg=1;for(int i:w) if(i<0){flg=0;break;}if(flg) ans+=fac[p]*inv[y-c[x]]%P*calc(w,p-y+c[x])%P*x;}}}else{int rig=lim?a[p]:9;for(int i=0;i<=rig;i++){bool tzro=zro&&!i;if(!tzro) c[i]++;ans+=dfs(p-1,lim&&i==rig,tzro);if(!tzro) c[i]--;}}return ans%P;
}
inline int solve(string& s){len=s.size();for(int i=0;i<len;i++) a[len-i]=s[i]-'0';return dfs(len,1,1);
}
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);fac[0]=inv[0]=1;for(int i=1;i<N;i++){//预处理阶乘及其逆元fac[i]=fac[i-1]*i%P;inv[i]=qp(fac[i],P-2);}cin>>t;while(t--){cin>>s;cout<<solve(s)<<"\n";}return 0;
}