程序设计实践考试的入门模板

本文提供了一份程序设计实践考试的入门模板,包括在线评测系统介绍、常见错误解析、题型分析、数学相关知识(素数、最大公约数、组合数)、数据结构(排序、双指针、前缀和、树状数组、二叉树、搜索算法)以及图论、动态规划等内容,旨在帮助应对程序设计实践考试的初学者。

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

这个博客不再更新,新博客地址请戳

程序设计实践考试的入门模板

前言

其实从大二开始就在整理有关如何学习C语言以及如何应对程序设计实践(和C语言考试)的经验和相关模板,由于各种原因,这件事情也没有一个很好的进展。前不久邹大佬提起这事儿的时候,突然觉得是应该好好整理一份类似于参考资料的东西了。

我打算先由自己整理出来这份模板,主要面向应对程序设计实践考试的同学。
本文当中可能会存在一些错误和遗漏的东西,还请指正。(email 1278683056@qq.com

使用这份模板之前,你需要学会最基本的C语言(C++)语法,所以关于语法部分如果还不是很熟悉,这份模板对你而言没有任何帮助。

在信工院程设挂科率奇高的大环境下,我觉得整理出一份适合于入门者使用的模板很有必要,希望能够帮助到大家。


第一章 关于程序设计入门

- 1.online judge

oj指的是在线评测系统,程序设计实践考试在oj上进行,所以首先我们需要对oj有一个大致的了解。

1.1 根据测试,xtuoj 1秒钟大约能够运行3e7次,这一点在避免得到TLE很重要,学会计算时间复杂度和空间复杂度是数据结构课程的内容,在此不赘述。

1.2 介绍几种常见错误的原因,以便于对症下药。

类型 原因 解决方案
WA(答案错误) 程序输出跟标程输出不一致,算法设计上有错误,或存在逻辑错误 改进算法,检查逻辑问题
TLE(超时) 程序未能在限定时间内结束,算法复杂度过高,或存在死循环 检查是否存在死循环,判断算法时间复杂度是否可行,如果确认复杂度可行,有可能是被卡常
RE(运行错误) 除0,栈溢出,内存访问越界等 ①检查除法运算的地方是不是除0了 ②如果使用了递归算法,判断是不是爆栈了 ③ 下标超过范围,数组开小,会访问越界
MLE(内存超限) 申请的内存超过了题目限制范围,一般是数组开大了,也可能是因为在死循环里不停地申请内存 改进算法,计算空间复杂度
PE(格式错误) 答案对了,但是输出格式有问题 仔细检查换行,空格等问题,距离AC很接近了

在此解释一下何为卡常
卡常指的是,程序算法本身复杂度符合题目要求,按理说是能够AC的,但可能由于自己代码写了很多不必要的东西,导致超时。当然,不排除会有出题人故意卡常。解决方法是尽量避免不必要的额外运算,另外,在输入输出上能通过使用外挂从而加速运行。外挂会在接下来的模板中给大家贴出。

何为爆栈:
递归层数太多,导致栈溢出。(这类似于死循环,但是程序还没超时就因为爆栈而终止运行了。)如果确实是因为层数太多,也可以手动模拟栈(stack),或者改为队列(queue)。

- 2.分析题型

程设考试一般6题,对于绝大多数人而言,通过2题意味着考试及格,当然也有少部分人可以1题及格。

一:暴力,所谓的签到题
二:执行
三:贪心
四:模拟
五:数据结构
六:图论
七:动态规划
八:数学相关

对于以上题型,一到四项没有什么很好的模板可供参考,更多的是平时的积累和练习,然而在考试时这些题相对后面的题型来说,属于简单题;针对五到八项,接下来我会整理出一些适合的模板。


第二章 数学相关

- 1 素数相关

1.1单个数n的判定,时间复杂度O(sqrt(N))

bool isprime(int n){
	if(n<2)return 0;
	if(n<4)return 1;
	for(int i=2;i*i<=n;i++){
		if(n%i==0)return 0;
	}
	return 1;
}

解释:
素数的因子只有1和它本身,那么如果从1到sqrt(n)都没有数字是n的因子,那么n一定是质数。
可以发现,一个数的所有因子,一定均等地分布在sqrt(n)的左右两边。
比如数字9的因子{1,3,9},左边是{1,3},右边是{3,9}。

1.2素数表,时间复杂度O(N)

const int maxn = 1e5+10;
bool notprime[maxn];
void getprime(){
	notprime[0]=notprime[1]=1;
	for(int i=2;i<maxn;i++){
		if(notprime[i]==0){
			for(long long j=1LL*i*i;j<maxn;j+=i){
				notprime[j]=1;	
			}
		}
	}
}

解释:
notprime[i]==1表示i不是素数,反之表示i是素数。
对于一个素数a,它的倍数一定都不是素数,所以我们可以对于遇见的每个素数,都把它的倍数标记为非素数,以上代码就是实现这一过程的。
由于i*i可能会溢出,为了避免溢出,j使用long long型。j从i^2开始,因为小于i倍的部分都已经被修改过了,不需要重复修改。

1.3 合数分解(值域为int的)
把一个合数a分解为 a = 1 * p1^x1 * p2^x2 * … *pn^xn 的形式

const int maxn = 1e5;
int p[100],x[100];
void getheshu(int n){
	int cnt=0;
	for(int i=2;i<maxn&&n>1;i++){
		if(n%i==0){
			p[++cnt]=i;
			while(n%i==0){
				n/=i;
				x[cnt]++;
			}
		}
	}
	if(n>1){
		p[++cnt]=n;
		x[cnt]=1;
	}
}

解释:
调用这个函数后,n的分解结果存储在p数组和x数组中,表达形式如上述。
如果能够分解出一个质数p,那么循环分解出的p的最高次幂。
最后剩下的“尾巴”如果大于1,说明这个数字一定是个质数。

- 2 最大公约数

gcd和lcm
最大公约数主要用到的是辗转相除法

int gcd(int a,int b){
	int c;
	while(b){
		c=a;
		a=b;
		b=t%b;
	}return a;
}

当然我们可以直接使用库函数__gcd(,)它是内部已经实现好了的函数,所以可以省去上面的代码,请注意该函数前面有两条下划线。

至于a和b的最小公倍数,等于a*b/gcd(a,b)
我们可以实现函数:

int lcm(int a,int b){
	return 1LL*a*b/gcd(a,b);//避免32位整型溢出
}

- 3 组合数

3.1组合数打表

对于较小的组合数,我们一般采用打表的方式存储答案,主要有以下两种方法:

int dp[30][30];
for(int i=0;i<30;i++){
	dp[i][0]=dp[0][i]=1;
}
for(int i=1;i<30;i++){
	for(int j=1;j<30;j++){
		dp[i][j]=dp[i-1][j]+dp[i][j-1];
	}
}
/**解释:dp[i][j]表示从i+j个物品中选择i个物品,不选择j个物品,
那么它可以由dp[i-1][j]和dp[i][j-1]转移得到,满足加法定理。
C(n,k)对应dp[n-k][k]
**/

第一种方法是我喜欢的写法,不过以下第二种方法可能更加方便。

int dp[30][30];
for(int i=0;i<30;i++){
	c[i][0]=c[i][i]=1;
	for(int j=1;j<i;j++){
		dp[i][j]=dp[i-1][j]+dp[i-1][j-1];
	}
}
/**解释:这种写法的C(n,k)对应的值是dp[n][k]
**/

以上打表的算法,时间复杂度都是O(n^2)的,所以当复杂度过高时,请使用卢卡斯定理。
另外,根据数据范围调整32位整型和64位整型,如果要求取模,记得每次运算都要取模。

下面我们介绍卢卡斯定理。

3.2 卢卡斯定理

具体原理可以自行百度学习,这里还牵涉到了乘法逆元的知识点,初学者不妨把它当作黑箱子来使用。

typedef long long ll;
const int maxn = 1e5+10;
const int mod  = 1e9+7;

ll qpow(ll a,ll n){
    ll ret=1;
    while(n){
        if(n&1)ret=ret*a%mod;
        a=a*a%mod;
        n>>=1;
    }
    return ret;
}

//除以一个数x,等同于乘以x的逆元,x的逆元 = x^(mod-2)%mod
//所以有p/q%mod = p*qpow(q,mod-2)%mod成立

ll fac[maxn];

void init(){
    fac[0]=1;
    for(int i=1;i<maxn;i++){
        fac[i]=fac[i-1]*i%mod;
    }
}

//C(n,m) = n!/(m!*(n-m)!)

ll C(ll n,ll 
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值