原题见洛谷(https://www.luogu.org/problem/show?pid=2822)
NOIP第二天第一题用到了数论的知识:
50分算法
最暴力的做法是直接用公式,求阶乘打打表什么的
65分算法
在暴力枚举的基础上,算组合数时一边除一边乘,时间复杂度 单组数据O(n^3)
进一步优化暴力的算法(高精)(最高分数:75)
在暴力枚举的基础上,算组合数时一边用高精度除一边用高精度乘,时间复杂度 单组数据O(n)
不过可能会爆空间,这取决你存的位数的多少
90分算法
我们组合数和杨辉三角间一一对应的关系杨辉三角可以递推得出公式大概就是f[i,j]=f[i-1,j]+f[i-1,j-1]之前把数组全部初始化成0,然后f[i,0]=1(i=0 to max)做一个初始化,组合数和杨辉三角间的关系是一一对应的Cij=f[i,j],在杨辉三角打表的时候把每个数字都对k取模,即f[i][j]=f[i-1][j]%k+f[i-1][j-1]%k=(f[i-1][j]+f[i-1][j-1])%k之后每次i从0到n,j从0到min(i,m)取出当前数字,若f[i][j]==0那么count++,一轮做完之后输出count即可这样子的做法第10和13个点超时。先贴代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int num[2005][2005];
int sum[2005][2005];
int k,t,m,n;
void deal(){
memset(num,0,sizeof(num));
for(int i=0;i<=2002;i++){
num[i][0]=1;
}
for(int i=1;i<=2002;i++){
for(int j=1;j<=i;j++){
num[i][j]=(num[i-1][j]+num[i-1][j-1])%k;
}
}
}
int main()
{
memset(sum,0,sizeof(sum));
int count=0;
scanf("%d%d",&t,&k);
deal();
for(int r=0;r<t;r++){
scanf("%d%d",&m,&n);
for(int i=0;i<=m;i++){
for(int j=0;j<=min(i,n);j++){
if(num[i][j]==0){
count++;
}
}
}
printf("%d\n",count);
count=0;
}
return 0;
}
100分算法
二维前缀和处理,之后每次去除的时间复杂度就是O(1),这样子总时间大概是90时间的3倍但是可以AC这道题(3110ms)
现在讲一下前缀和问题:
一维前缀和
这个优化主要是用来在O(1)时间内求出一个序列a中,a[i]+a[i+1]+……+a[j]的和。
具体原理十分简单:用sum[i]表示(a[1]+a[2]+……+a[i]),其中sum[0]=0,则(a[i]+a[i+1]+……+a[j])即等于sum[j]-sum[i-1]。
二维前缀和
同理,有一维就有二维。对于一个矩阵a,我们也能在O(1)时间内求出子矩阵[x1~x2][y1~y2]的和。
设sum[i][j]为子矩阵[1~i][1~j]的和。则由容斥原理得:
sum[0][j]=sum[i][0]=0
a[x1~x2][y1~y2]=sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]
应用问题
核心就两个字:降维。
面对许多高维问题,往往前缀和是最先想到的降维方法。
这样在降维的基础上,更进一步的优化才能实现。
我们现在用前缀和预处理每一个结果,到时候每一个输出就好了
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 2016;
int num[MAXN][MAXN];
int sum[MAXN][MAXN];
int main()
{
int T,k;
scanf("%d%d",&T,&k);
for(int i=0;i<MAXN;i++){
num[i][0]=0;
num[0][i]=1;
}
for(int i=1;i<MAXN;i++){
for(int j=1;j<MAXN;j++){
num[i][j]=(num[i][j-1]+num[i-1][j-1])%k;
}
}
for(int i=1;i<MAXN;i++){
for(int j=1;j<i;j++){
sum[j][i]=sum[j-1][i]+sum[j][i-1]-sum[j-1][i-1];
if (num[j][i]==0){
sum[j][i]++;
}
}
sum[i][i]=sum[i-1][i];
if(num[i][i]==0){
sum[i][i]++;
}
}
for(int i=0;i<T;i++){
int n,m;
scanf("%d%d",&n,&m);
while(m>n){
m--;
}
printf("%d\n",sum[m][n]);
//题目说明了0 <= j <= min(i,m),也就是m应要<=n;
}
return 0;
}