2018.10.31 bzoj4737: 组合数问题(lucas定理+容斥原理+数位dp)

传送门
这是一道让我重新认识 l u c a s lucas lucas的题。
考虑到 l u c a s lucas lucas定理:
( n m ) ≡ ( n % p m % p ) ∗ ( n p m p ) \binom n m \equiv \binom {n\%p} {m\%p}*\binom{\frac n p}{\frac m p} (mn)(m%pn%p)(pmpn) ( m o d (mod (mod p ) p) p)
所以可以看成 ( n m ) \binom n m (mn)在p进制下的表示
于是这道题就可以用这个方法转换成求 C ( i , j ) C(i,j) C(i,j)某一个进制位上满足 i p < j p i_p<j_p ip<jp的方案数。
然后可以通过容斥转一转变成求某一位 i p ≥ j p i_p\geq j_p ipjp的方案数。
于是就可以上数位 d p dp dp了。
代码:

#include
using namespace std;
typedef long long ll;
ll n,m;
const int mod=1e9+7,inv2=(mod+1)/2;
int T,k,f[70][2][2],sum[105][105],numn[70],numm[70],lenn=0,lenm=0;
inline int S(ll x){return x%=mod,(ll)x*(x+1)%mod*inv2%mod;}
inline int calc(ll a,ll b){return (S(a)-S(a-min(a,b))+mod)%mod;}
int main(){
	scanf("%d%d",&T,&k);
	for(int i=0;i<=k;++i)for(int j=0;j<=k;++j)sum[i][j]=calc(i,j);
	while(T--){
		scanf("%lld%lld",&n,&m);
		if(n<m)m=n;
		int ans=calc(n+1,m+1);
		memset(f,0,sizeof(f)),lenn=lenm=0;
		while(n)numn[++lenn]=n-n/k*k,n/=k;
		while(m)numm[++lenm]=m-m/k*k,m/=k;
		while(lenm<lenn)numm[++lenm]=0;
		f[n=lenn][1][1]=1;
		for(int i=n;i;--i){
			int upn=numn[i],upm=numm[i];
			f[i-1][1][1]=f[i][1][1]*(upn>=upm);
			f[i-1][1][0]=((ll)f[i][1][0]*(upn+1)%mod+(ll)f[i][1][1]*min(upn+1,upm)%mod)%mod;
			f[i-1][0][1]=((ll)f[i][1][1]*max(upn-upm,0)%mod+(ll)f[i][0][1]*(k-upm)%mod)%mod;
			f[i-1][0][0]=(((ll)f[i][0][0]*sum[k][k]%mod+(ll)f[i][0][1]*sum[k][upm])%mod+((ll)f[i][1][0]*sum[upn][k]%mod+(ll)f[i][1][1]*sum[upn][upm]%mod)%mod)%mod;
		}
		for(int i=0;i<2;++i)for(int j=0;j<2;++j)ans=(ans-f[0][i][j]+mod)%mod;
		printf("%d\n",ans); 
	}
	return 0;
}

你可能感兴趣的:(#,容斥原理,#,dp,#,lucas,#,组合数学,#,数学)