F小蒟蒻教你卡常
别看标题,那都是假的
以下的玩意儿转自:
3.
···
DALAO 1
如果编译器没有开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不用管。编译器会帮你)
x*10 <=> (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
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)
- 请尽力少用黑语法。
- 二分图匹配避免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);
后。
代码风格
初学者。
示例:
1 #include<cstdio> 2 using namespace std; 3 4 int main() 5 { 6 //Do something you want but please make sure it is right. 7 return 0; 8 }
上下括号请对齐。请保持缩进。
1 for(int i=1;i<=n;i++) 2 { 3 for(int j=1;j<=n;j++) 4 { 5 //Do something who can make you happy.(滑稽) 6 } 7 }
变量名函数名推荐按照 驼峰命名法
eg. checkOfInput()
函数名,变量名最好不要用没有意义的名字。比如,你要检查素数,函数名更好是checkPrime()
or isPrime()
这类的,而不是solve()
f()
当然也可以直接check()
但是当你有多个函数的时候为了不让自己混淆请使用最前面的方法。
还有变量名,比如说你要写动态规划,状态数组最好开成dp[][]..
这是大家约定俗成的。这样方便大家互相阅读。也方便别人帮你查错。
还有一些约定俗成的:
1 dfs()//deep-first-search 2 bfs()//bread-first-search 3 maxflow(),dinic()等//最大流 4 isprime(),getprime()//检查素数,筛素数 5 getdis()//计算欧几里德距离,曼哈顿距离 6 query()//查询操作 7 queryMax()/querySum() 8 update()//更新操作 9 tarjan()//有多种tarjan..找强联通分量/双联通分量/LCA的tarjan算法。 10 LCA()、RMQ()//字面意思.. 11 check()//一般是二分的check()函数 12 solve()//字面意思.. 13 match()//二分图匹配.. 14 gethash()//字面意思.. 15 getid()//字面意思.. 16 getrank()//字面意思.. 17 sort()//字面意思.. 18 pre()//预处理 19 20 dp[][] 一般是dp状态定义 或者f[][]/g[][] 21 dfn[] dfs序 que[]/q[]/sta[]/s[] 手写栈/队列 head,tail维护首尾。 22 //边 23 一般意义下: M->边 N->点 Q->操作数 24 struct Edge{ 25 int to,next,w; 26 }e[M] 27 struct Edge{ 28 int u,v,w; 29 }e[M] 30 #define maxn .. 31 #define N .. 32 #define M ... 33 #define mod ... 34 #define max3(a,b,c) max(a,max(b,c)) 35 #define isdigit(x) (x>='0'&&x<='9') 36 #define lson u<<1 37 #define rson u<<1|1
代码中插入适当的空格
1 for(int i = 1; i <= n; i++) 2 x = (a + b) / 2 3 ans = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))
当然上面有的地方空格可以略去,看个人习惯。
1 for(int i=1;i<=n;i++) 2 for(int i = 1;i <= n; i++) 3 for(int i = 1; i < = n ; i++) 4 for(int i = 1; i <= n ; i++) 5 for(int i=1;i <= n;i++) 6 7 #define rep(i,s,t) for(int i=(s);i<=(t);i++) 8 rep(i,1,n)
请不要在一行做过多的事。
1 for(int i = 1; i <= n ; i++)scanf("%d",&a[i]),a[i]<0?a[i]=-a[i]:1; 2 3 //别那样↑ 4 //============== 5 //应该这样↓ 6 7 8 for(int i = 1; i <= n ; i++) 9 scanf("%d",&a[i]),a[i]<0?a[i]=-a[i]:1; 10 11 12 for(int i = 1; i <= n ; i++){ 13 scanf("%d",&a[i]); 14 a[i]<0?a[i]=-a[i]:1; 15 } 16 for(int i = 1; i <= n ; i++) 17 { 18 scanf("%d",&a[i]); 19 if(a[i]<0)a[i] = -a[i]; 20 }
如果同样的计算要出现3次以上请写成函数。
最简单的例子是getdis(),abs()
这样做的好处是方便调试。
代码压行
这个是比较有争议的。代码风格好的可以跳过了。刚学的话请别这么干。否则你遇见bug后会放弃治疗。
为什么要压行?
时间短,浪费在代码风格上无意义。
所以。与上面对立的过程。
1 for(int i=1;i<=n;i++) 2 { 3 //do sth.. 4 } 5 6 ======================== 7 for(int i=1;i<=n;i++{ 8 //do sth.. 9 } 10 ======================== 11 #define rep(i,s,t) for(int i=(s);i<=(t);i++) 12 rep(i,1,n){ 13 //do sth.. 14 } 15 ========================= 16 #define rep(i,t) for(int i=1;i<=(t);i++) 17 rep(i,t){/*do sth..*/} 18 19 =========================== 20 =========================== 21 for(int i=head[u];~i;i=e[i].next){ 22 //do sth.. 23 } 24 25 ============================== 26 #define each(x) for(int i=head[x];~i;i=e[i].next) 27 each(u){ /*do sth ..*/} 28 29 ================================ 30 ================================ 31 int gcd(int a,int b) 32 { 33 if(!b)return a; 34 else return gcd(b,a%b); 35 } 36 37 ================================ 38 int gcd(int a,int b){return !b?a:gcd(b,a%b);} 39 ================================ 40 int gcd(int a,int b) 41 { 42 int t; 43 while(b!=0) 44 { 45 t = a; 46 a = b; 47 b = t%b; 48 } 49 } 50 =============================== 51 int gcd(int a,int b){for(int t;b!=0;t=a,a=b,b=t%b);} 52 ===============================
DALAO 2
- IO优化
- fread 和 fwrite ,如果还想再优化有mmap....(然而并不会用,好像也没用。。。)
- 读入优化(这个非常重要!!!!!!!)
1 inline int Read() 2 { 3 int x=0,f=1;char c=getchar(); 4 while(c>'9'||c<'0') {if(c=='-') f=-1;c=getchar();} 5 while(c>='0'&&c<='9') {x=x*10+c-'0'; c=getchar();} 6 return x*f; 7 }
- 输出优化好像用不到唉( ˇˍˇ )
-
inline
在声明函数之前写上inline修饰符(就像上面Read()一样),可以加快一下函数调用,但只能用于一些操作简单的函数。涉及递归,大号的循环等很复杂的函数,编译器会自动忽略inline。 -
register
在定义变量前写上register修饰符,用于把变量放到CPU寄存器中,适用于一些使用频繁的变量:1 register int n,m;
寄存器空间有限,如果放得变量太多,多余变量就会被放到一般内存中;
快,不是一般的快,快到什么程度呢?:1 register int a=0; 2 for(register int i=1;i<=999999999;i++) 3 a++; 4 int a=0; 5 for(int i=1;i<=999999999;i++) 6 a++;
结果:
优化:0.2826 second
不优化:1.944 second
恐怖啊!!!! -
循环展开
循环展开也许只是表面,在缓存和寄存器允许的情况下一条语句内大量的展开运算会刺激 CPU 并发(前提是你的 CPU 不是某 CPU)...
-
取模优化(仅O2)
1 //设模数为 mod 2 inline int inc(int x,int v,int mod){x+=v;return x>=mod?x-mod:x;}//代替取模+ 3 inline int dec(int x,int v,int mod){x-=v;return x<0?x+mod:x;}//代替取模-
-
前置 ++
后置 ++ 需要保存临时变量以返回之前的值,在 STL 中非常慢。事实上,int 的后置 ++ 在实测中也比前置 ++ 慢 0.5 倍左右(UOJ 上自定义测试)
-
不要开bool,所有bool改成char,int是最快的(原因不明)。
-
if()else语句比()?():()语句要慢,逗号运算符比分号运算符要快。
-
数据结构用指针代替数组(个人觉得无关紧要)
数组在用方括号时做了一次加法才能取地址!
所以在那些计算量超大的数据结构中,你每次都多做了一次加法!!!在 64 位系统下是 long long 相加,效率可想而知。
自己总结一点点
1.把%2改成&1
1 int a; 2 scanf("%d",&a); 3 printf("%d",a%2); 4 //上面已经用烂了 5 //============================================= 6 //试试这个 7 int a; 8 scanf("%d",&a); 9 printf("%d",a&1);
2.两个数交换
1 int a,b,t; 2 scanf("%d%d",&a,&b); 3 t=a; 4 a=b; 5 b=t; 6 //又是用烂了的法宝 7 //============================================= 8 //看看这个吧 9 int a,b; 10 scanf("%d%d",&a,&b); 11 a^=b; 12 b^=a; 13 a^=b;
3.用define const等简化代码
1 #define fp(i,l,r) for(register int i=(l);i<=(r);++i) 2 3 #define fd(i,l,r) for(register int i=(l);i>=(r);--i) 4 5 const int MAXN=1000 6 7 ···
4.结合多个函数,下面只是举个例子
1 inline int maxx(int a,int b){ 2 if(a>=b){ 3 return a; 4 } 5 else{ 6 return b; 7 } 8 } 9 10 inline int minn(int a,int b){ 11 if(a>=b){ 12 return b; 13 } 14 else{ 15 return a; 16 } 17 } 18 //maxx和minn的函数 19 //============================================= 20 //下面是整合 21 inline int botposs(int a,int b,int pd){ 22 if(pd==1) return a>=b?a:b; 23 if(pd==0) return a>=b?b:a; 24 }
5.神器:读入优化
1 #include <cstdio> 2 #include <iostream> 3 using namespace std; 4 void input(int &x){ 5 char c=getchar(); 6 x=0; 7 while(c<'0'||c>'9'){ 8 c=getchar(); 9 } 10 while(c<='9'&&c>='0'){ 11 x=x*10+c-48; 12 c=getchar(); 13 } 14 } 15 void output(int x){ 16 int num=0; 17 char c[105]; 18 while(x){ 19 c[++num]=(x%10)+48; 20 x/=10; 21 } 22 while(num){ 23 putchar(c[num--]); 24 } 25 } 26 int main(){ 27 int a,b; 28 input(a); 29 input(b); 30 output(a+b); 31 return 0; 32 }
3.4.两点仅仅是使代码看起来比较简洁