[SNCPC2019] Digit Mode 题解
模拟赛 T3,场切了。赛后得知原题后翻了一下题解区,发现居然没有和我一样的做法,于是写一篇题解造福后人。
下文记整数
考虑数位 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;
}