CF582D Number of Binominal Coefficients 题解

news/2024/9/30 10:50:46

CF582D Number of Binominal Coefficients 题解

纪念一下自己第一道独立 A 掉的黑题 / CF3300。

题目大意

给定质数 \(p\) 和整数 \(\alpha,A\),求满足 \(0 \le k \le n \le A\)\(p^{\alpha}|\binom nk\) 的数对 \((n,k)\) 的个数。

Solve

首先,我们引入 Kummer定理,即:

\(p\) 在组合数 \(n\choose m\) 中的幂次,恰好为 \(n\)\(m\)\(p\) 进制减法的借位次数。

所以我们只需统计 \(n\)\(k\)\(p\) 进制减法时借位次数大于等于 \(\alpha\)\((n,k)\) 的个数即可。

Step 1

\(A\)\(p\) 进制下数位分离,记其第 \(i\) 位为 \(a_i\)

我们考虑如下常规数位 dp:设 \(f(i,j,0/1,0/1)\) 表示:前 \(i+1\) 位,借位次数为 \(j\)\(n\) 是否卡满了 \(A\) 的上界,\(k\) 是否卡满了 \(n\) 的上界,这种状态的 \((n,k)\) 的个数。

但是,我们这一位是否借位,是和下一位(更小的那一位)是否借位有关的,如果下一位借位了,那么我们这一位相等时也可以借位,所以考虑多加一维,变为:\(f(i,j,0/1,0/1,0/1)\) 表示:前 \(i+1\) 位,借位次数为 \(j\)\(n\) 是否卡满了 \(A\) 的上界,\(k\) 是否卡满了 \(n\) 的上界,钦定这一位借位 / 不借位,这种状态的 \((n,k)\) 的个数。那么我们有如下朴素的转移:

枚举第 \(i\) 位的 \(n\)\(i'\)\(k\)\(j'\),有:

\[f(i-1,j,f1\vee[i'<a_i],f2\vee[j'<i'],f3)\longleftarrow f(i,j,f1,f2,0),\quad i'-f3-j'\geq0\\ f(i-1,j+1,f1\vee[i'<a_i],f2\vee[j'<i'],f3)\longleftarrow f(i,j,f1,f2,1),\quad i'-f3-j'<0 \]

\(f1,f2,f3\) 为枚举状态,常规的 \(f1,f2\)\(i',j'\) 的限制就不再讨论了,要注意的是 \(f3\),即是否钦定下一位借位,的限制。

时间复杂度约为 \(O(p^2\log_pA)\),但本题 \(p\leq 10^9\),考虑优化。

Step 2

上面的转移中,\(j'\) 的作用不是很大,所以我们只需枚举始状态 \(f1,f2,f3\),再枚举这一位上是否借位(\(y\)),\(j'<i'\) 是否成立(\(g2\)),下一位是否借位(\(g3\)),受算一下即可求出相应状态下合法的 \(j\) 的取值范围 \([l,r]\),那么我们有:

\[f(i-1,j+y,f1\vee[i'<a_i],f2\vee g2,g3)\longleftarrow (r-l+1)f(i,j,f1,f2,f3). \]

\(l\)\(r\) 是好确定的,简单写一下吧,为后面化简做准备。

\[l=\max \begin{cases} 0\\ i'+1-g3&f3=1\vee y=1\\ i'& g2=0\\ \end{cases}\\ r=\min \begin{cases} p-1\\ i'-g3&f2=0\vee f3=0\vee y=0\\ i'-1&g2=1 \end{cases} \]

时间复杂度约为 \(O(p\log_pA)\)。考虑更深一步讨论,尽量省去 \(i'\) 和一些参数的枚举。

Step 3

一步一步来,对参数分别讨论。上面的式子中,我们需保证 \(l\leq r\),所以据此讨论。

容易发现,当 \(f3=1\) 时,\(f2\) 也必须等于 \(1\),因为若 \(f3=1,f2=0\),那么 \(l\geq i'+1-g3,r\leq i'-g3\Longrightarrow l>r\)

同样的,我们有,\(y=f3\),也是对 \(l\)\(r\) 的第二行式子讨论可得。

并且,当 \(f3=1\) 时,\(g2\) 只能是 \(0\),因为若 \(g2=1\),那么 \(r=i'-1<l=i'+1-g3\)

由此,我们有了更优美的转移式:

\[f(i-1,j+1,f1\vee[i'<a_i],1,g3)\longleftarrow (r-l+1)\cdot f(i,j,f1,1,1).\\ f(i-1,j,f1\vee[i'<a_i],g2,g3)\longleftarrow (r-l+1)\cdot f(i,j,f1,0,0).\\ f(i-1,j,f1\vee[i'<a_i],1,g3)\longleftarrow (r-l+1)\cdot f(i,j,f1,1,0).\\ [l,r]= \begin{cases} [i'+1-g3,p-1]&f3=1\wedge g2=0\\ [i',i'-g3]&f3=0\wedge g2=0\\ [0,i'-1]&f3=0\wedge g2=1 \end{cases} \]

考虑把 \(l,r\) 的讨论转化为 \(r-l+1\),即系数的讨论,简单化简得:

\[\begin{align} &f(i-1,j+1,f1\vee[i'<a_i],1,g3)\longleftarrow (p-1-i'+g3)\cdot f(i,j,f1,1,1)\\ \iff &\begin{cases} f(i-1,j+1,f1\vee[i'<a_i],1,0)\longleftarrow (p-1-i')\cdot f(i,j,f1,1,1)\\ f(i-1,j+1,f1\vee[i'<a_i],1,1)\longleftarrow (p-i')\cdot f(i,j,f1,1,1)\\ \end{cases}\\ \\ &f(i-1,j,f1\vee[i'<a_i],0,g3)\longleftarrow (1-g3)\cdot f(i,j,f1,0,0)\\ \iff &f(i-1,j,f1\vee[i'<a_i],0,0)\longleftarrow f(i,j,f1,0,0)\\ \\ &f(i-1,j,f1\vee[i'<a_i],1,g3)\longleftarrow i'\cdot f(i,j,f1,0,0)\\ \iff &\begin{cases} f(i-1,j,f1\vee[i'<a_i],1,0)\longleftarrow i'\cdot f(i,j,f1,0,0)\\ f(i-1,j,f1\vee[i'<a_i],1,1)\longleftarrow i'\cdot f(i,j,f1,0,0)\\ \end{cases}\\ \\ &f(i-1,j,f1\vee[i'<a_i],1,g3)\longleftarrow (i'+1-g3)\cdot f(i,j,f1,1,0)\\ \iff &\begin{cases} f(i-1,j,f1\vee[i'<a_i],1,0)\longleftarrow (i'+1)\cdot f(i,j,f1,1,0)\\ f(i-1,j,f1\vee[i'<a_i],1,1)\longleftarrow i'\cdot f(i,j,f1,1,0)\\ \end{cases} \end{align} \]

时间复杂度仍约为 \(O(p\log_pA)\),只是优化了一些参数的枚举,枚举 \(i'\) 的瓶颈仍未拿下。

Step 4

化简之后,可以发现第四维的枚举用处不大,所以我们令 \(f'(i,j,f1,f2)=f(i,j,f1,0,f2)+f(i,j,f1,1,f2)\),有:

\[\begin{align} &\begin{cases} f'(i-1,j+1,f1\vee[i'<a_i],0)\longleftarrow (p-1-i')\cdot f'(i,j,f1,1)\\ f'(i-1,j+1,f1\vee[i'<a_i],1)\longleftarrow (p-i')\cdot f'(i,j,f1,1)\\ \end{cases}\\ &\begin{cases} f'(i-1,j,f1\vee[i'<a_i],0)\longleftarrow (i'+1)\cdot f'(i,j,f1,0)\\ f'(i-1,j,f1\vee[i'<a_i],1)\longleftarrow i'\cdot f'(i,j,f1,0)\\ \end{cases} \end{align} \]

然后,对 \(i'\) 的讨论就很明了了,分为 \(i<a_i\)\(i\geq a_i\) 两种情况即可,几个转移式的系数都是等差数列求和,很好算。

时间复杂度成功优化到约 \(O(log_pA)\)

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 的代码。

  • Step 1:\(O(p^2\log_pA)\)
  • Step 2:\(O(p\log_pA)\)
  • Step 3:\(O(p\log_pA)\)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/66445.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

PbootCms网站如何自动清理runtime缓存

要在 PbootCMS 中实现自动清理 runtime 缓存,可以通过以下步骤进行操作: 步骤 1: 修改 ExtLabelController.php 文件打开文件打开文件:\apps\home\controller\ExtLabelController.php找到 test() 方法找到以下代码:// 测试扩展单个标签 private function test() {$this->…

向带有BLE从机的代码中移植BackupOTA备份升级

目录 Backup升级方式,涉及到头/源文件的修改,代码改动量相比Onlyupdata升级方式来讲要更大。 Backup升级的优点:升级无需跳转,通过 基于24年9月9日的CH592EVT移植后的APP层工程见链接: 通过网盘分享的文件:592Peripheral_Extract_BackupOTA.zip链接: https://pan.baidu.c…

PbootCMS如何实现上传的文件使用原名称

要在 PbootCMS 中实现非图片类文件使用原名称保存,可以通过修改核心文件来实现。以下是具体的修改步骤和示例代码。 修改步骤打开文件打开文件:\core\function\file.php获取文件真实名称在 file.php 文件中找到以下代码:php$file_ext = strtolower(end($file)); // 获取扩展…

PBOOTCMS的网站站点地图Sitemap怎么用

在 PbootCMS 中,系统提供了动态站点地图功能,无需手动生成,直接访问特定 URL 即可实时获取站点地图。以下是具体的使用说明和示例代码。 使用说明访问动态站点地图动态站点地图支持多种格式(XML 或 TXT)。 访问以下 URL 即可实时获取站点地图:http://www.xxx.com/sitemap…

pbootcms模板如何在首页上调用公司简介等单页内容

在 PbootCMS 中,如果你想在首页上调用公司简介等单页内容,可以使用 pboot:content 标签来实现。以下是如何具体操作的步骤和示例代码。 调用单页内容 1. 使用 pboot:content 标签id=1:指定要调用的单页内容的 ID。 len=300:指定显示的内容长度,单位为字符数。 dropHTML=1:…

pbootcms模板首页如何调用全站所有的文章

在 PbootCMS 中,如果你想在首页调用全站所有的文章,可以使用 pboot:list 标签,并设置 scode=* 来指定调用所有栏目中的文章。以下是如何具体操作的步骤和示例代码。 调用全站所有文章 1. 使用 pboot:list 标签scode=*:表示调用全站所有文章。 num=5:表示显示的文章数量。扫…

Rancher迁移(单点到集群集群到集群迁移)

Rancher迁移概述:本文用于记录rancher从docker迁移到HA架构、从HA架构到HA架构的过程,便于后续回溯。 一、部署架构 1、docker run 运行rancher直接使用(不推荐生产,单节点发生故障,则其他节点上将没有可用的集群数据副本,并且你可能会丢失 Rancher Server 上的数据。) …

Leetcode 981. 基于时间的键值存储

1.题目基本信息 1.1.题目描述 设计一个基于时间的键值数据结构,该结构可以在不同时间戳存储对应同一个键的多个值,并针对特定时间戳检索键对应的值。 实现 TimeMap 类:TimeMap() 初始化数据结构对象 void set(String key, String value, int timestamp) 存储给定时间戳 time…