suxxsfe

一言(ヒトコト)

P6669 组合数问题

https://www.luogu.com.cn/problem/P6669
数位dp+lucas定理

给顶 \(n,m,k\),求 对于 \(0\le i\le n,0\le j\le \min(i,m)\),有多少对 \((i,j)\) 满足 \(\tbinom{i}{j}\)\(k\) 的倍数

其实要求的就是 \(\tbinom{i}{j}\bmod k=0\) 的个数
由于 \(k\) 是小质数,所以可以根据 lucas 定理,转换为要求有多少 \(i=(a_1 a_2\cdots a_p)_k\le n,j=(b_1 b_2\cdots b_p)_k\le \min(i,m)\),使得 \(\dbinom{a_1}{b_1}\cdot \dbinom{a_2}{b_2}\cdots \dbinom{a_p}{b_p}\bmod k=0\)
其中,\((a_1 a_2\cdots a_p)_k,(b_1 b_2\cdots b_p)_k\)\(i,j\)\(k\) 进制展开形式,\(p\) 取它们之间较大的位数
那么上述式子 \(\bmod k\)\(0\),当且仅当存在一个 \(u\),使得 \(a_u<b_u\)

然后就是做一个 \(k\) 进制下的数位dp,\(f(pos,ok,ij,in,jm)\) 表示当前是第 \(pos\) 位,满足条件的 \(u\) 是否已经出现过,后三位分别表示 \(i,j\)\(i,n\)\(j,m\) 之间顶不顶上界
就可以记忆化搜索了

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN puts("")
inline long long read(){
	register long long x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=getchar();}
	return y?x:-x;
}
#define mod 1000000007
int k;
long long n,m;
long long f[68][2][2][2][2];
int nn[68],mm[68];
long long work(int pos,int ok,int ij,int in,int jm){
	if(!pos) return ok;
	long long &ret=f[pos][ok][ij][in][jm];
	if(~ret) return ret;
	ret=0;
	int limi=in?nn[pos]:(k-1),limj=jm?mm[pos]:(k-1);
	for(reg int i=0;i<=limi;i++)for(reg int j=0;j<=limj&&(j<=i||!ij);j++){
		ret=(ret+work(pos-1,ok|(i<j),ij&(i==j),in&(i==limi),jm&(j==limj)))%mod;
	}
	return ret;
}
int main(){
	int T=read();k=read();
	while(T--){
		n=read();m=read();
		std::memset(nn,0,sizeof nn);std::memset(mm,0,sizeof mm);
		nn[0]=mm[0]=0;
		while(n) nn[++nn[0]]=n%k,n/=k;
		while(m) mm[++mm[0]]=m%k,m/=k;
		std::memset(f,-1,sizeof f);
		printf("%lld\n",work(std::max(nn[0],mm[0]),0,1,1,1));
	}
	return 0;
}
posted @ 2020-11-29 14:36  suxxsfe  阅读(324)  评论(0编辑  收藏  举报