常數優化的一些技巧

前言html

  • 卡常數是OIer的基本素質之一,但一些人對其很不瞭解。
  • 本文介紹了一些基本的卡常技巧,更適用於初學者。
  • 文中如有不恰當的地方請及時指出,博主會盡快更正。
  • 喜歡就點個推薦唄~
  • 不喜歡請在評論區隨便dis千萬別點反對啊

一.STLios

  • (附:爲了方便理解,一些數據結構博主用的是c++封裝成STL時所用的函數名,若是想借鑑的話請不要開萬能庫,否則會瘋狂CE)
  • 原則上手寫要比用STL快,不過有些確實難打···
  • set直接用就好,都是基於紅黑樹實現(根本不會打),效率已經足夠高(固然不懼碼量的巨佬也能夠手打)。
#include<cstdio> #include<cstdlib>
using namespace std; #define L tree[x].l
#define R tree[x].r
int const N=2e5+5; int root,n,opt,st,t; struct Treap{ int l,r,id,weight,size; }tree[N]; inline int apply(int x){ int k=++t; tree[k].id=x,tree[k].weight=rand(); tree[k].size=1; return k; } inline void get(int x){ tree[x].size=tree[L].size+tree[R].size+1; return ; } void split(int x,int val,int &a,int &b){ if(!x){ a=b=0; return ; } if(tree[x].id<=val){ a=x; split(R,val,R,b); } else{ b=x; split(L,val,a,L); } get(x); return ; } int merge(int x,int y){ if(!x || !y)return x+y; if(tree[x].weight<tree[y].weight){ R=merge(R,y); get(x); return x; } tree[y].l=merge(x,tree[y].l); get(y); return y; } void insert(int x){ int a,b; split(root,x,a,b); root=merge(merge(a,apply(x)),b); return ; } void del(int y){ int a,b,x; split(root,y,a,b); split(a,y-1,a,x); root=merge(merge(a,merge(L,R)),b); return ; } int rk(int x){ int a,b,ans; split(root,x-1,a,b); ans=tree[a].size+1; root=merge(a,b); return ans; } int find(int x,int y){ while(tree[L].size+1!=y) if(y<=tree[L].size)x=L; else y-=tree[L].size+1,x=R; return tree[x].id; } int pre(int x){ int a,b,ans; split(root,x-1,a,b); ans=find(a,tree[a].size),root=merge(a,b); return ans; } int nxt(int x){ int a,b,ans; split(root,x,a,b); ans=find(b,1),root=merge(a,b); return ans; } int main(){ scanf("%d",&n); while(n--){ scanf("%d%d",&opt,&st); switch(opt){ case 1: insert(st); break; case 2: del(st); break; case 3: printf("%d\n",rk(st)); break; case 4: printf("%d\n",find(root,st)); break; case 5: printf("%d\n",pre(st)); break; case 6: printf("%d\n",nxt(st)); break; } } return 0; }
爲證實本身是不懼碼量的巨巨強行肝的一棵不是紅黑樹的普通平衡樹
  • map確實很方便,不過有一種神奇的東西叫作hash表,能夠以近似$\Theta(1)$的效率進行查詢^_^(就是有點不穩定……)。
  • unorder_map理論複雜度確實也是常數級別的,但實際上比手打的Hash錶慢了很多,若是有足夠的時間建議手打。
#include<cstdio>
using namespace std; int const mod=7e5+1,N=1e5+5; int head[mod+2],Next[N],to[N],t; inline int up(int x){ return x<0?x+mod:x; } inline int ins(int x){ int z=up(x%mod); for(register int i=head[z];i;i=Next[i]) if(to[i]==x)return i; Next[++t]=head[z],head[z]=t; to[t]=x; return t; } int main(){ int n; scanf("%d",&n); for(register int i=1;i<=n;++i){ int x; scanf("%d",&x); printf("%d\n",ins(x));//返回離散後對應的值
 } return 0; }
View Code
  • 堆(優先隊列)能夠考慮手寫,不過大部分狀況直接用就行。
  • 但手寫堆也有好處,就是快啊能夠刪除堆中的元素。
  • upd.居然被B哥dis……仍是說明一下,博主堆的刪除是須要知道元素在堆中的位置的,這個記錄一下就行了,應該沒人不會吧……
    博主稍懶沒有打見諒見諒。
#include<cstdio> #include<cstring>
using namespace std; int const N=1e5+5; template<class T> inline void swap(T &x,T &y){ T z=x; x=y,y=z; return ; } template<class T>
struct priority_queue{                    //大根堆
 T heap[N]; int n; inline void clear(){        //清空
        n=0; return ; } inline bool empty(){        //判斷是否爲空
        return !n; } inline int size(){            //返回元素個數
        return n; } inline void up(int x){        //向上調整
        while(x^1) if(heap[x]>heap[x>>1])swap(heap[x],heap[x>>1]),x>>=1; else return ; } inline void down(int x){    //向下調整
        int s=x<<1; while(s<=n){ if(s<n && heap[s]<heap[s|1])s|=1; if(heap[s]>heap[x]){swap(heap[s],heap[x]);x=s,s<<=1;} else return ; } } inline void push(T x){    //插入元素x
        heap[++n]=x; up(n); return ; } inline T top(){return heap[1];}                        //返回堆中的最大值
    inline void pop(){heap[1]=heap[n--];down(1);return ;}   //刪除堆頂
    inline void erase(int x){                                //刪除下標爲x的節點
        heap[x]=heap[n--]; up(x),down(x); return ; } inline T* begin(){                //返回堆中第一個元素的指針(實在不會搞迭代器……)
        return &heap[1]; } inline T* end(){                    //返回堆的尾部邊界
        return &heap[n+1]; } inline T &operator [] (int x){ return heap[x]; } }; int main(){ //freopen("1.in","r",stdin); //freopen("1.out","w",stdout);
    int t; priority_queue<int>q; q.clear(); //注意全部手打的數據結構用以前須要clear
    scanf("%d",&t); for(register int i=1;i<=t;++i){ int z; scanf("%d",&z); q.push(z); } for(register int* i=q.begin();i!=q.end();++i)    //遍歷1
        printf("%d ",*i); puts(""); for(register int i=1;i<=q.size();++i)            //遍歷2
        printf("%d ",q[i]); puts(""); while(!q.empty()){                                //從大到小輸出
        printf("%d ",q.top()); q.pop(); } puts(""); return 0; }
View Code
  • upd.博主發現有時候要開好多好多堆,像上面那樣開靜態數組會炸。因此博主又用vector實現了一下。
    只是實現了STL中優先隊列的動態內存,刪除任意點操做沒有打,和上面大同小異。
    至於爲何博主這麼不走心……由於博主不會指針因此不會動態數組,而vector實現的手寫堆並不比系統堆快……
struct Pri_Q{
    int tp;
    vector<ll>hp;
    inline void _Clear(){tp=0;hp.push_back(0ll);return ;}
    inline bool Empty(){return !tp;}
    inline void down(int x){
        int y=x<<1;
        while(y<=tp){
            if(y^tp && hp[y]<hp[y|1])y|=1;
            if(hp[x]<hp[y])_Swap(hp[x],hp[y]),x=y,y<<=1;
            else return ;
        }
    }
    inline void up(int x){
        while(x^1){
            if(hp[x>>1]<hp[x])_Swap(hp[x>>1],hp[x]),x>>=1;
            else return ;
        }
    }
    inline void Pop(){return hp[1]=hp[tp--],hp.pop_back(),down(1);}
    inline void Push(ll x){return hp.push_back(x),up(++tp);}
    inline ll Top(){return hp[1];}
    inline int Size(){return tp;}
}q[100002];
View Code
  • 雙端隊列手打稍清奇,須要把l,r指針初始化到元素數量的位置。
  • 雙端隊列的STL也挺好用的,其實手打不是頗有必要……
  • 仍是附個代碼吧。
#include<cstdio>
using namespace std; int const N=1e5+5; template<class T>
struct deque{ int l,r; T a[N]; inline void clear(){l=r=N>>1;return ;} inline bool empty(){return l==r;} inline void push_back(T x){a[r++]=x;return ;} inline void push_front(T x){a[--l]=x;return ;} inline void pop_front(){++l;return ;} inline void pop_back(){--r;return ;} inline T front(){return a[l];} inline T back(){return a[r-1];} inline int size(){return r-l;} inline T &operator [] (int x){return a[l+x-1];} inline T* begin(){return &a[l];} inline T* end(){return &a[r];} }; int main(){ deque<int>q; q.clear(); return 0; }
View Code
  • 至於stack和queue,必須手寫!!!
  • 注意stack打法,do-while循環更加方便取出棧頂元素。
#include<iostream>
using namespace std; int stack[1000],top;//
int q[1000],t,u;    //隊列
int main(){ ios::sync_with_stdio(false); cin.tie(0); int n; cin>>n; for(register int i=1;i<=n;++i){ cin>>q[++t]; stack[top++]=q[t]; } do{ cout<<stack[--top]<<" "; }while(top); cout<<endl; while(u^t)cout<<q[++u]<<" "; return 0; }
View Code

  • vector其實不是很快,有個題我沒用vecotr但比用vector的多個sort,結果比開vector的快1倍(雖然我二分手打sort隨機化還加了fread)。
  • 因此內存容許的話直接開2維數組(但vector確實挺方便,也不能總犧牲碼量和內存優化時間吧,因此想用就用)。
  • pair也挺好,不過自定義結構體更快。
  • 總之,c++內置數據結構的改爲手打必定變快,除非你打錯了或者自帶巨大常數……
  • B哥也手打了各種STL並且有真正的紅黑樹!不過紅黑樹這東西也只能刷題的時候打着爽一爽,考場上不會有人考慮吧……

二.運算c++

  • mod定義成const。
  • 能乘不除,能加減別用魔法模法。
  • 能位運算就別用加減乘除···
  • x2^n改爲<<n。
  • /2^n改爲>>n。
  • swap(x,y)改爲x^=y^=x^=y。
  • 模數若爲2^n能夠直接&(mod-1)。
  • 也能夠先開unsigned int最後取模。
  • 兩個小於模數相加和將值域爲(-mod,mod)的數值域改爲[0,mod)能夠用下面代碼中的取模優化。
inline int down(int x){ return x<mod?x:x-mod; } inline int up(int x){ return x<0?x+mod:x; }
View Code
  • 數據範圍不大能夠開long long,中間不取模最後再取。
  • 判斷奇偶&1。
  • i!=-1改成~i。
  • !=直接改爲^。

三.讀入算法

  • 別用cin,用cin就在主函數加:
ios::sync_with_stdio(false);
cin.tie(0);
  • 打着還麻煩,因此就用scanf或快讀。
  • 不超過1e5的數據scanf便可。
  • 再大了最好用快讀。
  • 記得位運算優化···
  • 還嫌慢上fread(不過這個玩意有時候還會有意想不到的奇效,好比讓你的程序慢十倍,慎用)。
  • 用fread後沒法鍵入,請初學者不要擔憂,freopen就行。
  • 快讀還有一些妙用,好比定義常量不能scanf但可用快讀賦值,這在一些讀取模數的題目中頗有用。
  • 下面代碼裏快讀讀的是非負整數,讀整數特判一下有無‘-’便可,就不給出實現了。
#include<cstdio> #include<iostream>
using namespace std; int const L=1<<20|1; char buf[L],*S,*T; #define getchar() ((S==T&&(T=(S=buf)+fread(buf,1,L,stdin),S==T))?EOF:*S++) inline int read(){ int ss=0;char bb=getchar(); while(bb<48||bb>57)bb=getchar(); while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar(); return ss; } int main(){ ios::sync_with_stdio(false); cin.tie(0); int n; cin>>n; n=read(); puts("233"); return 0; }
View Code
  • 良心博主出於善意放了個讀整數的:
inline int read(){ int ss(0),pp(1);char bb(getchar()); for(;bb<48||bb>57;bb=getchar())if(bb=='-')pp=-1; while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar(); return ss*pp; }
View Code

四.輸出數組

  • 同理,用printf別用cout。
  • 快輸是個危險東西,搞很差還會變慢。
  • 慎用非遞歸版快輸,輸不了零。
  • 不過非遞歸快一點,實在不行特判~
#include<cstdio> #include<iostream>
using namespace std; char ch[20],top; inline void write(int x){        //非遞歸 
    while(x){ ch[top++]=x%10; x=x/10; } do{ putchar(ch[--top]+48); }while(top); puts(""); return ; } void out(int x){                //遞歸 
    if(x>=10)out(x/10); putchar(x%10+48); return ; } int main(){ int n=233; write(n); out(n); return 0; }
View Code
  • upd. skyh大神表示非遞歸版改爲do-while循環就能輸出0了,%%%
#include<bits/stdc++.h>
using namespace std; char ch[100],top; inline void write(int x){ do{ ch[top++]=x%10; x/=10; }while(x); do{ putchar(ch[--top]+48); }while(top); return ; } signed main(){ write(5);write(2);write(0); return 0; }
View Code
  • 有fread同理也有fwrite。
  • upd.非遞歸快輸打while也是能夠輸0的,當時啥也不會意淫的……
#include<cstdio> #include<algorithm>
int const L=1<<20|1; char buf[L],z[22],zt; int t=-1; int a[22]; inline void write(int x){ if(x<0)buf[++t]='-',x=-x; while(z[++zt]=x%10+48,x/=10); while(buf[++t]=z[zt--],zt); buf[++t]=32; } int main(){ int n; scanf("%d",&n); for(register int i=1;i<=n;++i)scanf("%d",&a[i]); std::sort(a+1,a+n+1); for(register int i=1;i<=n;++i)write(a[i]); fwrite(buf,1,t+1,stdout); return 0; }
fwrite+非遞歸

五.dp數據結構

  • 其實已經不算卡常了,能夠說是剪枝···
  • 1.排除冗雜
  • 能不dp的就別dp。
  • 說白了就是for循環裏多設幾個限制條件。
  • 好比可憐與超市一題,yzh巨佬重設了個tmp數組實現$\Theta(N^2)$轉移,還證實了一波複雜度,%%%
  • 但其實$\Theta(N^3)$可過···
#include<cstdio> #include<cstring>
using namespace std; int const N=5005,lar=0x3f3f3f3f,L=1<<20|1; char buf[L],*S,*T; #define getchar() ((S==T&&(T=(S=buf)+fread(buf,1,L,stdin),S==T))?EOF:*S++) inline int read(){ int ss=0;char bb=getchar(); while(bb<48 || bb>57)bb=getchar(); while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar(); return ss; } inline void swap(int &x,int &y){ int z=x; x=y,y=z; return ; } inline int max(int x,int y){ return x>y?x:y; } inline int min(int x,int y){ return x<y?x:y; } int n,m,pp; int c[N],d[N],f[N][N][2]; int head[N],Next[N],to[N],t; int siz[N],lim[N]; inline void add(int x,int y){ to[++t]=y; Next[t]=head[x],head[x]=t; return ; } void dfs(int x){ int y,now=2; siz[x]=1; f[x][1][0]=c[x]; f[x][1][1]=c[x]-d[x]; for(int i=head[x];i;i=Next[i]){ dfs(y=to[i]); siz[x]+=siz[y=to[i]]; for(register int j=siz[x];j>=0;--j){ int lit=min(now,j); for(register int k=(j>lim[y])?j-lim[y]:1;k<lit;++k){ int o=j-k; f[x][j][0]=min(f[x][j][0],f[y][o][0]+f[x][k][0]); f[x][j][1]=min(f[x][j][1],min(f[y][o][0],f[y][o][1])+f[x][k][1]); } f[x][j][0]=min(f[x][j][0],f[y][j][0]); } for(register int j=siz[x];j>=0;--j) if(f[x][j][0]<=m || f[x][j][1]<=m){now=j+1;break;} } for(register int i=1;i<=siz[x];++i) if(f[x][i][1]>=m && f[x][i][0]>=m){lim[x]=i;return ;} lim[x]=siz[x]; return ; } int main(){ memset(f,0x3f,sizeof(f)); n=read(),m=read(),c[1]=read(),d[1]=read(); for(register int i=2;i<=n;++i){ c[i]=read(),d[i]=read(); add(read(),i); } dfs(1); for(register int i=lim[1];i>=0;--i) if(f[1][i][0]<=m || f[1][i][1]<=m){ printf("%d",i); return 0; } }
View Code
  • 2.等效替代
  • 提及來很模糊···
  • 以HAOI2015樹上染色爲例。
  • 染黑點和染白點其實同樣。
  • 因此你徹底能夠加一句k=min(k,n-k);

六.初始化app

  • 單個變量能夠直接初始化,好像比賦值初始化略快。
  • 小範圍初始化數組直接memset。
  • 對於單一數組memset就是比for循環要快,不要懷疑!!!
  • 有時後你以爲for循環快,那不是由於數據水與極限數據相差太遠就是由於你連清了五個以上數組。
  • 清大量範圍相同的數組才採用for。
  • 對於一些題目你以爲memset用sizeof(數組名)清數組很浪費也能夠改爲sizeof(int)*長度,不過通常沒有必要。
  • 固然一些狀況你徹底能夠邊for邊初始化。
  • 最典型的就是矩陣乘法:
struct ljj{ int a[101][101]; friend ljj operator * (ljj a1,ljj a2){ ljj c; for(register int i=1;i<=100;++i) for(register int j=1;j<=100;++j){ c.a[i][j]=0; for(register int k=1;k<=100;++k) c.a[i][j]+=a1.a[i][k]*a2.a[k][j]; } } };
View Code

七.排序dom

  • 動態維護的用堆或者平衡樹,堆最好手打,平衡樹只要不自帶大常數就能夠手打(一大哥手打Splay T飛改爲用set就A了)。
  • 靜態能夠sort,歸併希爾並不推薦(主要是我不會···)。
  • 固然一些算法如CDQ能夠邊分治邊歸併的就別sort了。
  • sort結構體時注意最好重載運算符,定義比較函數比較慢。
  • 值域小的桶排序。
  • 至於基數排序,看狀況吧,一些題目仍是要用的。
  • 關於sort還有一個神奇操做,叫作隨機化快排。
  • 大量用sort且待排序的數比較多得話能夠考慮一下,由於直接用普通快排容易棧溢出。
  • 順帶一提,隨機化快排對有序數列的排序比普通快排快上個幾百倍。
  • 說白了就是隨機化快排不容易被特殊數據卡。
  • 再說白了就是能比普通快排多騙點分…
#include<bits/stdc++.h>
using namespace std; int a[1000001]; int random_div(int *q,int l,int r){ int z=l+rand()%(r-l+1),zl=l-1,tp; swap(q[z],q[r]),tp=q[r]; for(register int i=l;i<r;++i) if(q[i]<=tp) ++zl,swap(q[zl],q[i]); swap(q[++zl],q[r]); return zl; } void Random_Sort(int *q,int l,int r){ if(l<r){ int z=random_div(q,l,r); Random_Sort(q,l,z-1); Random_Sort(q,z+1,r); } return ; } int ran(int x){ return (long long)rand()*rand()%x; } int main(){ srand(time(NULL)); int n; scanf("%d",&n); for(register int i=1;i<=n;++i)scanf("%d",&a[i]); Random_Sort(a,1,n); for(register int i=1;i<=n;++i) printf("%d ",a[i]); puts(""); return 0; }
View Code
  • 然而以上都是針對手打快排,事實上c++內置的sort比手打的快排要飛快不少……
  • 固然隨機化給咱們帶來的啓示是,只在sort前加個random_shuffle有時候會讓sort更加飛快(也有可能讓你T飛)。

八.編譯優化ide

  • 慎用!!!!!!!!
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
View Code
  • 這個的最大用處是:當你用上這些還TLE時,你能夠放棄了,由於你的程序已經沒救了。

九.其餘函數

  • 其實這裏纔是精華。
  • inline和register儘可能用。
  • 注意inline不要在遞歸函數裏用。
  • register不要在遞歸過程當中用,回溯時能夠用。
  • 自加自減運算符前置,就是i++改爲++i。
  • 內存容許,值域不大且不需memset時bool改int。
  • 能int絕對不要用long long(一哥們數顏色(洛谷上兔子那個,不是帶修莫隊)調了一下午一直TLE,我把他long long改爲int就A了……)。
  • 若是方即可以循環展開,偶爾有奇效。
  • if-else能改就改爲三目運算符?:
  • 邊權爲1的圖跑最短路用bfs別用dijkstra。
  • (一個名叫Journeys的題的暴力bfs比dijstra高10分···)
  • 稠密圖建圖vector實現鄰接表要比不用vector快不少,由於vector遍歷時內存連續。
  • 倍增lca進行倍增時從深度的log值開始。
  • 多維數組順序按for循環來。
  • eg. CodeForces 372CWatching Fireworks is fun
  • dp[N][M]與dp[M][N]一個TLE60,一個AC。
  • 其實就是內存訪問的連續性,一直跳躍着訪問內存天然慢。
  • 數組維數越少越好。
  • 離散化能夠利用pair記錄下標,映射時更加方便,不用再lower_bound了。
  • 還有一個玄學操做叫作卡時,就是你打了個dfs或者隨機化,裏面用clock()判斷運行時間,快TLE的時候直接輸出當前答案。
  • 注意clock()返回的時間不是特別準,別把跳出時間定的太極端。
  • 還有注意Linux下1e6是一秒,想象一下跳出時間設成1000的酸爽。。

結束語

  • 到這裏卡常的內容已經結束了,但博主還想說說本身對卡常的見解。
  • 不得不說卡常有它的侷限性。
  • 就像蒟蒻與神犇的區別,卡常數是名副其實的底層優化,它不能將指數級算法優化成多項式級別,甚至改變不了時間複雜度的任何一個字母。
  • 可是,咱們卻不能忽視它,由於它能讓一些看似沒法優化的程序絕地逢生。
  • 而卡常的做用本是錦上添花,但更多人將其視做騙分的手段,由於卡常確實能讓暴力更快,多過一些測試點。
  • 正因如此,卡常成了暴力的代名詞,爲一些神犇所不齒。這是卡常的悲劇性。
  • 不過,至少在我看來,愈來愈多的人開始注意常數,開始去卡常,這讓我莫名欣慰,由於博主仍是很喜好卡常的。
  • 然而卡常雖好,但它終究是卡,有常數纔會卡,咱們須要的是,在構造出程序的過程當中減小常數。
  • 卡常的最高境界,是告別卡常,由於打出的程序已經再也不須要卡常。
  • 這須要將減小常數看成一種習慣,在編寫程序的過程當中無時無刻不在關注着常數,寫出本身最完美的程序。
  • 培養這種習慣,這也是卡常真正的意義所在吧。
  • 以上。
相關文章
相關標籤/搜索