abc194f O(nk)题解
前言
洛谷唯一的题解似乎是 \(O(nk^2)\) 的,怎么卡过去的orz
这里提供一种与 AT 官方题解时间复杂度相同的 \(O(nk)\) 做法。
Solution
题意很显然,就不解释了。
一眼丁真,考虑数位 dp。
设 \(dp_{i,j}\) 表示做到第 \(i\) 位,不同的个数有 \(j\) 种的方案数。
显然有转移方程:
\(dp_{i,j}=dp_{i-1,j}\times j +dp_{i-1,j-1} \times (16-j-1)\)
边界为 \(dp_{1,1}=15\)。
分类讨论,考虑存在前导 0 与不存在前导 0。
当前导 0 存在时,由于每种方案中最高位数字的方案数是相等的,因此显然总方案数为 \(\frac{15}{16}\sum_{i=1}^{n}dp_{i,k}\)。
而当不存在时,将数字分为已出现过和未出现过两种,分别计算个数。
两种唯一的区别为已钦定选择的数字个数。则在第 \(i\) 位时,若前面已有 \(x\) 个数已被选,方案数为 \(\binom{16-x}{k-x} \times \sum_{j=k-x}^{\min(n-i,k)} \frac{dp_{n-i,j}\times\binom{x}{j-k+x}}{\binom{16}{j}}\),具体可以用组合意义推导得到。
应该还可以再化简,但是这样就已经能做了。
实际实现的时候由于组合数的上下标不超过 16,因此可以提前预处理。
code
#include <bits/stdc++.h>
#define LL long long
using namespace std;
LL n,i,j,k,m,tmp1,tmp2,tmp3,ans=0;
LL mod=1000000007;
LL val[17],C[17][17],invC[17][17],dp[200005][17];
string s;
bool flag[17];
void exgcd(LL a,LL b,LL &x,LL &y,LL &val){
if(b==0){
val=a;
x=1;
y=0;
}
else{
exgcd(b,a%b,y,x,val);
y-=x*(a/b);
}
}
LL doit(char ch){
if(ch>='0' && ch<='9') return ch-'0';
else return ch-'A'+10;
}
LL inv(LL x){
tmp1=0,tmp2=0,tmp3=0;
exgcd(x,mod,tmp1,tmp2,tmp3);
return (tmp1+mod)%mod;
}
int main() {
val[0]=1;
for(i=1;i<=16;i++){
val[i]=val[i-1]*i%mod;
val[i]%=mod;
}
for(i=1;i<=16;i++)
for(j=0;j<=i;j++)
C[i][j]=val[i]*inv(val[i-j])%mod*inv(val[j])%mod;
for(i=1;i<=16;i++)
for(j=0;j<=i;j++)
invC[i][j]=inv(C[i][j]);
cin>>s>>k;
n=s.length();
dp[0][0]=1;
dp[1][1]=16;
C[0][0]=1;
invC[0][0]=1;
for(i=2;i<=n;i++)
for(j=1;j<=16;j++){
dp[i][j]=(dp[i-1][j]*j%mod+dp[i-1][j-1]*(16-(j-1))%mod);
dp[i][j]%=mod;
}
for(i=1;i<n;i++){
ans+=(dp[i][k]*inv(16)%mod*15ll%mod)%mod;
ans%=mod;
}
ans+=((dp[n-1][k-1])*1ll*C[15][k-1]%mod*invC[16][k-1]%mod+(dp[n-1][k]*1ll*C[15][k-1]%mod*invC[16][k])%mod)%mod*(doit(s[0])-1)%mod;
ans%=mod;
LL sum1,sum2,now=1;
flag[doit(s[0])]=true;
for(i=1;i<s.length();i++){
sum1=0,sum2=0;
for(j=0;j<doit(s[i]);j++){
if(flag[j]==true){
sum1++;
}
else sum2++;
}
for(j=k-now;j<=k;j++){
ans+=sum1*dp[n-i-1][j]%mod*C[16-now][k-now]%mod*C[now][j-(k-now)]%mod*invC[16][j]%mod;
ans%=mod;
}
if(now+1<=k){
for(j=k-now-1;j<=k;j++){
ans+=sum2*dp[n-i-1][j]%mod*C[16-now-1][k-now-1]%mod*C[now+1][j-(k-now-1)]%mod*invC[16][j]%mod;
ans%=mod;
}
}
if(flag[doit(s[i])]==false) now++;
flag[doit(s[i])]=true;
if(now>k) break;
}
if(now==k) ans++;
printf("%lld",ans%mod);
return 0;
}