把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

luogu P2822 组合数问题

题面传送门
对于这道题,其实题目很简单,暴力很好打。
想法\(1\):对于每组输入数据,暴力判断两重循环枚举,一重循环计算组合数。时间复杂度\(O(tn^3)\),大概\(30\)
想法\(2\):把每组数的组合存下来,直接调用。时间复杂度\(O(tn^2)\)大概\(35\)分。
想法\(3\):既然直接算组合数会爆精度,那么在计算组合数是就不能有除法和减法,只有加法和乘法满足\((a\%c)(b\%c)\%c=(ab)\%c\)\((a\%c+b\%c)\%c=(a+b)\%c\).然后我们自然想起杨辉三角,这是一个求组合数的东西,递推公式为\(f_{i,j}=f_{i-1,j}+f+{i-1,j-1}\),所以我们可以递推出来,一遍递推一遍%k,对于每组数据递推一遍,若\(\%k==0\)即有一个答案。大概有\(70\)分。
想法\(4\):因为杨辉三角是一样的,所以可以先递推出来。
递推出来有什么用啊,还不是要\(O(n^2)\)查询。
这可是提高组原题,提高组不考数据结构考什么。
那么有一种数据结构,修改很麻烦,但查询\(O(1)\)
所以我们可以用前缀和过掉这一题。
关于二维前缀和之前的题解已经有讲过,不再赘述。
代码实现:

#include<cstdio>
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
int n,m,x,y,f[2039][2039],s[2039][2039],ans,tot,pus;
int main(){
	register int i,j;
	scanf("%d%d",&n,&m);
	f[1][0]=f[1][1]=1;
	for(i=2;i<=2000;i++){
		f[i][0]=1;
		for(j=1;j<=i;j++){
			f[i][j]=(f[i-1][j]+f[i-1][j-1])%m;
			if(!f[i][j]) s[i][j]=1;
			s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
		}
		s[i][i+1]=s[i][i];
	}
	for(i=1;i<=n;i++){
		scanf("%d%d",&x,&y);
		printf("%d\n",s[x][min(x,y)]);
	}
	return 0;
}

顺带提一下,当\(y>x\)的时候解为\(s_{x,x}\),因为在\(x\)个数中选\(y\)个数一定选不出来。
评测记录
然而我们发现每次都要近似\(2000\times2000\)的循环太浪费时间,有些时候没必要递推这么多,所以我们可以记录最大的\(n\),并按此递推(真心怕出题人毒瘤)
代码实现:

#include<cstdio>
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
int n,m,x[10039],y[10039],f[2039][2039],s[2039][2039],ans,tot,pus;
int main(){
	register int i,j;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]),ans=max(x[i],ans);
	f[1][0]=f[1][1]=1;
	for(i=2;i<=ans;i++){
		f[i][0]=1;
		for(j=1;j<=i;j++){
			f[i][j]=(f[i-1][j]+f[i-1][j-1])%m;
			if(!f[i][j]) s[i][j]=1;
			s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
		}
		s[i][i+1]=s[i][i];
	}
	for(i=1;i<=n;i++)printf("%d\n",s[x[i]][min(x[i],y[i])]);
	return 0;
}

评测记录
数据小的点就跑得飞起。
附:前缀和题解

posted @ 2020-03-14 13:08  275307894a  阅读(55)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end