bzoj 4314: 倍数?倍数! 数学

本文介绍了一种通过组合数学解决特定计数问题的方法,利用f(k,n,p)函数表示从0到N-1中选取不同整数的方案数,并考虑其在模意义下的解的存在性和数量,最终给出了一段AC代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       我们可以把集合改成序列,然后答案再/K!,这样就简单很多了。注意此时1,2,2算两次。

       实际上,考虑前K-1个数,我们随意取,这样最后一个数实际上是固定的;但是可能会出现重复的情况,那么此时我们统计最后两个数重复,也就是前K-2个数随意取,且满足前K-2个数的和S使得S+2x0(mod N)有解的方案。注意到该方程有解当且仅当S≡0(mod gcd(2,N)),那么对于特定的S方程的解的个数为gcd(2,N)……

       注意到这里面所有的统计都可以归结为f(k,n,p)表示取k个不同的0~N-1之间的数,其中有且仅有一个数出现p次的答案;那么我们k-1个随意取但需要满足前k-1个数的和S≡0(mod gcd(p,n)),也就是f(k-1,gcd(p,n),1)*方程解的个数;然后减去有一个数出现p+1次的答案f(k-1,n,p+1)*出现p+1次的数出现的位置(即k-1),然后注意边界即可。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define mod 1000000007
#define N 1005
using namespace std;

int n,m,f[N][N],ptt[N];
int pw(int x,int y){
	int t=1; for (; y; y>>=1,x=(ll)x*x%mod) if (y&1) t=(ll)t*x%mod;
	return t;
}
int gcd(int x,int y){ return (y)?gcd(y,x%y):x; }
int solve(int x,int y,int z){
	if (!x) return 1;
	if (y==1) return ptt[x];
	int d=gcd(y,z);
	if (z==1){
		int t=min(y,1001);
		if (f[x][t]==-1){
			f[x][t]=((ll)solve(x-1,d,1)*(m/y*d)-(ll)solve(x-1,y,2)*(x-1))%mod;
			if (f[x][t]<0) f[x][t]+=mod;
		}
		return f[x][t];
	}
	return (((ll)solve(x-1,d,1)*(m/y*d)-(ll)solve(x-1,y,z+1)*(x-1))%mod+mod)%mod;
}
int main(){
	scanf("%d%d",&m,&n);
	memset(f,-1,sizeof(f));
	int i,tmp=1;
	ptt[1]=m;
	for (i=2; i<=n; i++) ptt[i]=(ll)ptt[i-1]*(m-i+1)%mod;
	for (i=1; i<=n; i++) tmp=(ll)tmp*i%mod;
	printf("%d\n",(ll)solve(n,m,1)*pw(tmp,mod-2)%mod);
	return 0;
}


by lych

2016.5.24

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值