CF582D Number of Binominal Coefficients 题解
CF582D Number of Binominal Coefficients 题解
纪念一下自己第一道独立 A 掉的黑题 / CF3300。
题目大意#
给定质数 和整数 ,求满足 且 的数对 的个数。
Solve#
首先,我们引入 Kummer定理,即:
在组合数 中的幂次,恰好为 与 做 进制减法的借位次数。
所以我们只需统计 与 做 进制减法时借位次数大于等于 的 的个数即可。
Step 1#
将 在 进制下数位分离,记其第 位为 。
我们考虑如下常规数位 dp:设 表示:前 位,借位次数为 , 是否卡满了 的上界, 是否卡满了 的上界,这种状态的 的个数。
但是,我们这一位是否借位,是和下一位(更小的那一位)是否借位有关的,如果下一位借位了,那么我们这一位相等时也可以借位,所以考虑多加一维,变为: 表示:前 位,借位次数为 , 是否卡满了 的上界, 是否卡满了 的上界,钦定这一位借位 / 不借位,这种状态的 的个数。那么我们有如下朴素的转移:
枚举第 位的 填 , 填 ,有:
为枚举状态,常规的 对 的限制就不再讨论了,要注意的是 ,即是否钦定下一位借位,的限制。
时间复杂度约为 ,但本题 ,考虑优化。
Step 2#
上面的转移中, 的作用不是很大,所以我们只需枚举始状态 ,再枚举这一位上是否借位(), 是否成立(),下一位是否借位(),受算一下即可求出相应状态下合法的 的取值范围 ,那么我们有:
和 是好确定的,简单写一下吧,为后面化简做准备。
时间复杂度约为 。考虑更深一步讨论,尽量省去 和一些参数的枚举。
Step 3#
一步一步来,对参数分别讨论。上面的式子中,我们需保证 ,所以据此讨论。
容易发现,当 时, 也必须等于 ,因为若 ,那么 。
同样的,我们有,,也是对 和 的第二行式子讨论可得。
并且,当 时, 只能是 ,因为若 ,那么 。
由此,我们有了更优美的转移式:
考虑把 的讨论转化为 ,即系数的讨论,简单化简得:
时间复杂度仍约为 ,只是优化了一些参数的枚举,枚举 的瓶颈仍未拿下。
Step 4#
化简之后,可以发现第四维的枚举用处不大,所以我们令 ,有:
然后,对 的讨论就很明了了,分为 和 两种情况即可,几个转移式的系数都是等差数列求和,很好算。
时间复杂度成功优化到约 。
Code#
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
char c=getchar();
int now=0;short f=1;
while(c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9') now=(now<<1)+(now<<3)+(c^48),c=getchar();
return now*f;
}
const int N=3350,MOD=1e9+7,M=1010;
using ll=long long;
int p,c,f[2][N/*借位次数*/][2/*i=A*/][2/*这一位是否需要借位*/],n,a[N],ans;
struct zzn//高精度封装,只需实现 除以低精 和 对低精取模,好写的
{
int num[M],len;
zzn(){len=0;memset(num,0,sizeof num);}
inline void read()
{
char s[M];scanf("%s",s+1);
len=strlen(s+1);
for(int i=1;i<=len;i=-~i) num[i]=s[len-i+1]-'0';
}
inline void print()
{
for(int i=len;i;i=~-i)
printf("%d",num[i]);
}
zzn operator/(const int b)const
{
zzn res;res.len=len;ll r=0;
for(int i=len;i;i=~-i)
r=10ll*r+num[i],res.num[i]=r/b,r%=b;
while(!res.num[res.len]&&res.len) res.len=~-res.len;
return res;
}
int operator%(const int b)const
{
int res=0;
for(int i=len;i;i=~-i) res=(10ll*res+num[i])%b;
return res;
}
}A;
signed main()
{
p=read();c=read();A.read();
while(A.len) a[n=-~n]=A%p,A=A/p;
f[n&1][0][0][0]=1;
for(int now=n;now;now=~-now)
for(int x=0;x<=n-now;x=-~x)
for(int f1=0;f1<2;f1=-~f1)
{
int t0=f[now&1][x][f1][0],t1=f[now&1][x][f1][1];
f[now&1][x][f1][0]=f[now&1][x][f1][1]=0;
int i0=(f1?p-1:a[now]);//i枚举上界
//对于i<a[now]
(f[now&1^1][x][1][1]+=1ll*(a[now]-1)*a[now]/2%MOD*t0%MOD)%=MOD,
(f[now&1^1][x][1][0]+=1ll*(a[now]+1)*a[now]/2ll%MOD*t0%MOD)%=MOD;
(f[now&1^1][x+1][1][1]+=1ll*(p*2-a[now]+1)*a[now]/2%MOD*t1%MOD)%=MOD,
(f[now&1^1][x+1][1][0]+=1ll*(p*2-a[now]-1)*a[now]/2%MOD*t1%MOD)%=MOD;
//对于i>=a[now]
(f[now&1^1][x][f1][1]+=1ll*(a[now]+i0)*(i0-a[now]+1)/2%MOD*t0%MOD)%=MOD,
(f[now&1^1][x][f1][0]+=1ll*(a[now]+i0+2)*(i0-a[now]+1)/2%MOD*t0%MOD)%=MOD;
(f[now&1^1][x+1][f1][1]+=1ll*(p*2-a[now]-i0)*(i0-a[now]+1)/2%MOD*t1%MOD)%=MOD,
(f[now&1^1][x+1][f1][0]+=1ll*(p*2-a[now]-i0-2)*(i0-a[now]+1)/2%MOD*t1%MOD)%=MOD;
//下为暴力枚举 i 的转移
// for(int i=0;i<=i0;i=-~i)
// {
// (f[now&1^1][x+f2][f1|(i<a[now])][1]+=1ll*i*t0%MOD)%=MOD,
// (f[now&1^1][x+f2][f1|(i<a[now])][0]+=1ll*(i+1)*t0%MOD)%=MOD;
// (f[now&1^1][x+f2][f1|(i<a[now])][1]+=1ll*(p-i)*t1%MOD)%=MOD,
// (f[now&1^1][x+f2][f1|(i<a[now])][0]+=1ll*(p-i-1)*t1%MOD)%=MOD;
// }
}
for(int i=c;i<=n;i=-~i)
for(int j=0;j<2;j=-~j)
(ans+=f[0][i][j][0])%=MOD;
return printf("%d",ans),0;
}
再附上不同 Step 的代码。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】