信息学竞赛的常数优化、常见问题、代码风格等

如果编译器没有开O2优化
用库函数常数会凭空增加很多。。
似乎NOIP考场不开O2
某些时候,如果你优化到无法再优化的时候
尝试去自己重新实现库函数。

  • isdigit()
  • max()/min()
  • unique()/lower_bound()/upper_bound()
  • scanf()/printf()
  • cin/cout
  • getchar()/putchar()
  • STL::queue/stack/priority_queue/deque

##常数优化:

位运算

没有O2的时候(有O2不用管。编译器会帮你)
x10 <=> (x<<3)+(x<<1)
x!=y <=> x^y
x!=-1 <=> ~x
x
2 <=> x<<1
x*2+1 <=> x<<1|1
x/2 <=> x>>1
(x+1)%2 <=> x^1
x%2 <=> x&1
【upd 2018-11-16】thx to Cwolf9.原文此处有误
x%2==0 <=> !(x&1)

##语法

inline 在非递归函数前加修饰。
循环变量 int i =>register int i

c++没有尾递归优化。所以可以自己手写栈来优化递归。

原则上尽量减少乘/除/取模 指令
取模指令如果是逐渐累加的话,

x+=add;x%=mod;
=> x+=add;x>=mod?x%=mod:1;

A?B:C 好像要比if,else语句快。

memset初始化细节 memset(a,0x3f,sizeof(a));
最后的a[1] = 0x3f3f3f3f
int的极限是 0x7fffffff
还可以~0u
INF有的时候不要刚好赋值到0X7FFFFFFF,如果有2个inf的值相加就会溢出。

乘法溢出。
这个要注意。不要直接全部long long这样慢很多

关于类型转换,一般不用管,编译器会处理。
但是,考试环境有点老?没试过,所以如果有不同类型的话最好在前面显示的强转一下。

赋值>int的话请在数字后加LL

【Update1】2016-11-13

下面的不是绝对,环境不同可能不会出错。
建议平时在编译的时候把编译指令加上 "-ansi"

###信息学竞赛的一些注意事项:[有误请指正]

  • pow()函数请慎用,低版本有的时候会CE。

  • 考场不允许使用**"bits/stdc++.h",并且使用该库变量名可能不能使用next** (C++库里面有个template是next会CE)
    【upt:2018-11-16】听学弟说现在可以用万能头文件了。

  • 请尽力少用黑语法。

  • 二分图匹配避免link做变量名(还有个什么变量名Linux也会CE我突然记不到了…有时其实也可以用**“中国式的变量名命名法”**这样不会CE。 不推荐这种诡异的风格),Linux环境可能会CE。

  • 少用“math.h”|“cmath”库。因为_x,_y,y1,y2,x1,x2,x0,y0,这类命名有时会CE。

  • 考场严禁使用带下划线的库函数。eg. __gcd()

  • 编程时利用宏可以减少代码量,但是请务必在每个变量里加括号。 eg. #define rep(i,s,t) for(int i=(s);i<=(t);i++)

  • 循环变量for(int i;…;…;)请不要放到全局上。这种常数不会卡。相反会带来很多隐式的错误

  • 如果你不精通指针请少用它。指针的代码很难查错。竞赛里面请避免使用函数指针,多级指针,指针数组这样的语法。

  • 如果可以静态实现,请先考虑静态版本的代码。而不是写动态。(malloc() new

  • 引用和指针不是一个东西。这个语法我已经不想解释了。去买本语法书细读。

  • 考试少用C++的OOP特性,可以使用STL template<> class namespace 但不推荐使用。

  • 请熟悉STL里面的 string queue stack vector set map 后面这些用的少,仅供参考并且在pascal选手消失前应该是不会考的前面这些只是方便才用,但请注意常数!推荐自己实现。 deque multiset multimap bitset

  • 宏指令少用,#progma 肯定是禁了的,别想手动扩栈。涉及操作编译器和系统的函数都要挂。

  • 内嵌汇编也是算作弊处理,毕竟这是算法竞赛,不是信息安全竞赛,也不是编程能力竞赛。

下面的话来自一位编程大神有可能我记错了或者大神说错了…
然后有的童鞋认为memset()既然这么容易错,那为什么我们还要用呢,直接for一遍初始化。请注意,memset()底层是用汇编实现的效率要比直接的快4倍,不是所有的库函数都是c\c++实现的。
(我记错了吗…还是strlen() 记不到了QAQ,但是memset实现应该不是直接for,肯定有很多常数优化和位运算)
哦,然后就是strlen() 重点! 像下面这种代码复杂度是o(nL)的,L为str的长度。
for(int i=0;i<strlen(str);i++
已经有很多人还是写的上面的这种代码却一直不知情。等你被卡了就知道了。

最后还有一个问题。由于没有开O2优化,会导致一些本来没有区别的变得比较明显。多维数组请把大的放前面。eg. int dp[10000][10][2] 而不是 dp[2][10][10000],常数差距0.5s。比算法的差距还大。开了o2后差别不明显。

还有一个坑。那些将cin/cout和scanf()/printf()一起用的朋友们,如果你们的代码再怎么查都查不出错了,这个时候要考虑是不是把c++和c的IO函数混用了。这个会导致一些潜在性的问题。尤其是当你用了ios::sync_with_stdio(false); 后。

##代码风格
初学者。
示例:

#include<cstdio>
using namespace std;

int main()
{
    //do sth..
    return 0;
}

上下括号请对齐。请保持缩进。

for(int i=1;i<=n;i++)
{
   for(int j=1;j<=n;j++)
   {
     //do sth..
   }
}

变量名函数名推荐按照 驼峰命名法 eg. checkOfInput()
函数名,变量名最好不要用没有意义的名字。比如,你要检查素数,函数名更好是checkPrime() or isPrime() 这类的,而不是solve()
f() 当然也可以直接check() 但是当你有多个函数的时候为了不让自己混淆请使用最前面的方法。
还有变量名,比如说你要写动态规划,状态数组最好开成dp[][]..
这是大家约定俗成的。这样方便大家互相阅读。也方便别人帮你查错。
还有一些约定俗成的:

dfs()//deep-first-search
bfs()//bread-first-search
maxflow(),dinic()等//最大流
isprime(),getprime()//检查素数,筛素数
getdis()//计算欧几里德距离,曼哈顿距离
query()//查询操作
queryMax()/querySum()
update()//更新操作
tarjan()//有多种tarjan..找强联通分量/双联通分量/LCA的tarjan算法。
LCA()、RMQ()//字面意思..
check()//一般是二分的check()函数
solve()//字面意思..
match()//二分图匹配..
gethash()//字面意思..
getid()//字面意思..
getrank()//字面意思..
sort()//字面意思..
pre()//预处理

dp[][] 一般是dp状态定义 或者f[][]/g[][]
dfn[] dfs序 que[]/q[]/sta[]/s[] 手写栈/队列 head,tail维护首尾。
//边
一般意义下: M->边 N->点 Q->操作数
struct Edge{
    int to,next,w;
}e[M]
struct Edge{
    int u,v,w;
}e[M]
#define maxn ..
#define N ..
#define M ...
#define mod ...
#define max3(a,b,c) max(a,max(b,c))
#define isdigit(x) (x>='0'&&x<='9')
#define lson u<<1
#define rson u<<1|1


  • 代码中插入适当的空格
for(int i = 1; i <= n; i++)
x = (a + b) / 2
ans = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))

当然上面有的地方空格可以略去,看个人习惯。

for(int i=1;i<=n;i++)
for(int i = 1;i <= n; i++)
for(int i = 1; i < = n ; i++)
for(int i = 1; i <= n ; i++)
for(int i=1;i <= n;i++)

#define rep(i,s,t) for(int i=(s);i<=(t);i++)
rep(i,1,n)
  • 请不要在一行做过多的事。
for(int i = 1; i <= n ; i++)scanf("%d",&a[i]),a[i]<0?a[i]=-a[i]:1;

==>
for(int i = 1; i <= n ; i++)
	scanf("%d",&a[i]),a[i]<0?a[i]=-a[i]:1;

for(int i = 1; i <= n ; i++){
	scanf("%d",&a[i]);
	a[i]<0?a[i]=-a[i]:1;
}
for(int i = 1; i <= n ; i++)
{
	scanf("%d",&a[i]);
	if(a[i]<0)a[i] = -a[i];
}
  • 如果同样的计算要出现3次以上请写成函数。
    最简单的例子是getdis(),abs()

这样做的好处是方便调试。
##代码压行
这个是比较有争议的。代码风格好的可以跳过了。刚学的话请别这么干。否则你遇见bug后会放弃治疗。

###为什么要压行?
时间短,浪费在代码风格上无意义。
所以。与上面对立的过程。

for(int i=1;i<=n;i++)
{
	//do sth..
}

========================
for(int i=1;i<=n;i++{
	//do sth..
}
========================
#define rep(i,s,t) for(int i=(s);i<=(t);i++)
rep(i,1,n){
	//do sth..
}
=========================
#define rep(i,t) for(int i=1;i<=(t);i++)
rep(i,t){/*do sth..*/}

===========================
===========================
for(int i=head[u];~i;i=e[i].next){
	//do sth..
}

==============================
#define each(x) for(int i=head[x];~i;i=e[i].next)
each(u){ /*do sth ..*/}

================================
================================
int gcd(int a,int b)
{
	if(!b)return a;
	else return gcd(b,a%b);
}

================================
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
================================
int gcd(int a,int b)
{
	int t;
	while(b!=0)
	{
	    t = a;
	    a = b;
	    b = t%b;
	}
}
===============================
int gcd(int a,int b){for(int t;b!=0;t=a,a=b,b=t%b);}
===============================



评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

真·skysys

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值