【第十三届蓝桥杯备战】C/C++解题时的一些个人小技巧和注意事项(持续更新中)

【第十三届蓝桥杯备战】C/C++解题时的一些个人小技巧和注意事项(持续更新中)


先说一些废话:我最近觉得写算法题就像是打格斗游戏一样,格斗主要分为 确认确认后的连续进攻两个过程。 确认考验的是选手迅速切入问题的能力,也就是基本确定了使用什么算法来解决问题;而 确认后的连续进攻即将自己练习的连招准确无误地打出来,这个考验的是选手的熟练度。对于算法高手而言,他们的算法积累无疑都是非常深的,手速也很快,因此对他们而言,谁的 确认问题的速度更快,谁就在比赛中更有优势。但对于笔者这种菜鸟来说,即便碰巧偷了问题的“下盘”,也往往会在使用连招过程中失误,从而导致 断连,最终被问题一击KO。为什么会 断连?简单来说,是因为练习不够,但可以进一步细分为两部分:一个是算法熟练度低,另一个是没有形成一个快速稳健的编码习惯,比如总是在输入输出上卡壳,调试时总是反复手敲数据等等,当然这主要还是对语言掌握不深以及经验不足所致。

本篇不着重于具体问题的算法流程,主要分享个人认为的算法之外的一些注意点以及编码技巧。确保在已知解题算法的前提下能用“一套连招快速带走”题目,而不至于惨死在算法之外的方方面面。这样也可以让我们把更多精力放在算法设计方面,避免浪费大量时间。

输入输出

  1. 考虑到效率问题,标准输入输出尽量用scanf()printf(),且尽量不要将std::cinscanf()以及std::coutprintf()混着用(有时会出现问题,比如下面的情况,用于加速std::cin和std::cout,此时绝对不能把C和C++的输出混着用,具体实例见[1])。
	std::ios::sync_with_stdio(false);  // 取消cout和printf的兼容,此后不能将输出流混用
	std::cin.tie(0);  // 解除std::cin和std::cout的绑定
  1. long long类型的输入输出:scanf("%I64d", &n);。注意是字母I(i),不是l(L),且一定是大写。注:在洛谷做题时,%I64d要改为%lld

  2. 输出带前导零的整数,比如时间格式HH::MM::SS,打印语句应为printf("%02d:%02d:%02d\n", h, m, s);,其他情况依此类推。

数组

  1. 有时候需要预先开一个比较大的数组,大小常常由宏来表示,如下代码所示。如果用#define MAXN (1e5 + 5)会抛出异常size of array 'A' has non-integral type 'double',因为数组下标必须为整数类型。
    #define MAXN 100005
    int A[MAXN];
    
  2. 数组命名尽量避免与STL中的命名冲突,比如leftrightcountmap等。

数据结构

1. 线段树

注意原始输入数组A的下标,线段树模板的根节点下标通常为1,则数组A的起始点下标尽量也从1开始;若A从下标0开始,则需要进行统一下标的操作。比如下面这段代码,其实现了点修改的线段树,由于输入数组从0开始,故需要在建树和调用时进行下标转换,原题见 leetcode307.区域和检索。

class NumArray {
private:
    int n;
    typedef struct{
        int l, r;
        int sumv;
    }tree;
    #define MAXN 30001
    tree T[MAXN*4];
    // 建树
    void build(int o, vector<int>& A){
        int L = T[o].l, R = T[o].r;
        int lc = o*2, rc = o*2+1;
        if(L == R){
            T[o].sumv = A[L-1]; // 1:下标要减1, A[0...n-1]
            return;
        }    
        int M = L + (R - L) / 2;
        T[lc].l = L, T[lc].r = M;
        T[rc].l = M+1, T[rc].r = R;
        build(lc, A);
        build(rc, A);
        T[o].sumv = T[lc].sumv + T[rc].sumv;
    }
    void pushup(int o){
        T[o].sumv = T[o*2].sumv + T[o*2+1].sumv;
    }
    void update(int o, int x, int val){
        int L = T[o].l, R = T[o].r;
        if(L == R){
            T[o].sumv = val;
            return;
        }
        else{
            int M = L + (R - L) / 2;
            if(x <= M){
                update(o*2, x, val);
            }
            else{
                update(o*2+1, x, val);
            }
            pushup(o);
        }
    }
    int query(int o, int ql, int qr){
        int L = T[o].l, R = T[o].r;
        if(ql <= L && R <= qr){
            return T[o].sumv;
        }
        int M = L + (R - L) / 2;
        int sumv = 0;
        if(ql <= M){
            sumv += query(o*2, ql, qr);
        }
        if(qr > M){
            sumv += query(o*2+1, ql, qr);
        }
        return sumv;
    }
    
public:
    NumArray(vector<int>& nums) {
        n = nums.size();
        T[1].l = 1, T[1].r = n;
        build(1, nums);
    }
    
    void update(int index, int val) {
        update(1, index+1, val); // 2:下标index要加1
    }
    
    int sumRange(int left, int right) {
        return query(1, left+1, right+1);  // 3:0 <= left, right < n,要加1
    }
};

调试

  1. 可以用freopen函数进行输入输出流的重定向,把数据放在文件里,笔者这里给个简易调试模板,默认情况下在执行代码的当前文件夹下新建一个名称为in1.txt的文件,用于存放用例输入,注意数据文件第一行为用例的数目;提交代码时只需要把#define DEBUGMODE注释掉即可。
    #include<iostream>
    #include<cstdio>  // freopen()在此标准库中 
    using namespace std;
    #define DEBUGMODE  // 提交代码时注释掉即可
    
    int main(){
    	int tCase = 1; // 测试用例数目 
    	#ifdef DEBUGMODE
    	freopen("in1.txt", "r", stdin);
    	scanf("%d", &tCase);
    	//freopen("out1.txt", "w", stdout); // 如果想把结果保存到文件,取消该句注释 
    	#endif	
    	while (tCase--){
    		// 在这里放置算法的代码
    		// ...
    	}
    	#ifdef DEBUGMODE
    	cin.get();  // 用stdout时,避免界面直接关闭,从而看不到输出结果
    	cin.get();  
    	#endif
    	return 0;
    }
    

如果本文有描述不妥的地方,或是有更好的方案,欢迎交流与指正!

References

[1] LT-Y. sync_with_stdio(false)的副作用[EB/OL]. https://www.cnblogs.com/Little-Turtle–QJY/p/13832888.html, 2020-10-17.

  • 7
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值