ACM选手进阶指北:一个好的代码库与latex维护代码文档
Posted 入侵检测引擎算法工程师/前ICPC选手
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ACM选手进阶指北:一个好的代码库与latex维护代码文档相关的知识,希望对你有一定的参考价值。
一个好的代码库是必须的,打的久的人心里自然有b数
而且对于算法模板而言,简单的文件夹分级维护就能满足所有需求了
当然,好的代码库不只是把代码堆进去那么简单,
还需要随用随取,变量名不冲突,风格一致,封装优秀等等
并且每次写题都用自己的板子,不断精进细节
非常推荐使用封装,默认参数,宏定义,有意义的变量名,统一取名习惯和常数名等等
例如主席树的模板代码,直接复制粘贴就能用,也不会和你写了一半的其他代码冲突:
int rt[maxn];//@树根@ class ptree{public: #define nd node[now] #define ndp node[pre] #define mid (s+t)/2 int cnt; struct segnode{int l,r,sum;}node[maxn*30]; void maketree(int s,int t,int &now=rt[0]){ now=++cnt;nd={s,t,0}; if(s==t) return ; maketree(s,mid,nd.l); maketree(mid+1,t,nd.r); } void update(int pos,int val,int s,int t,int &now,int pre){ now=++cnt;nd=ndp;nd.sum+=val; if(s==t) return ; if(pos<=mid) update(pos,val,s,mid,nd.l,ndp.l); else update(pos,val,mid+1,t,nd.r,ndp.r); } ll query(int l,int r,int s,int t,int now,int pre){ if(l<=s&&r>=t) return nd.sum-ndp.sum; ll sum=0; if(l<=mid) sum+=query(l,r,s,mid,nd.l,ndp.l); if(r>mid) sum+=query(l,r,mid+1,t,nd.r,ndp.r); return sum; } #undef mid }tree;
再比如重链剖分的模板,用了默认参数,也写了一点注释,用的时候只需要改update和query就行了:
class graph{public://@按点@ struct node{int to,next;}e[maxn<<1]; int head[maxn],nume,mp[maxn]; inline void add(int a,int b){ e[++nume]={b,head[a]}; head[a]=nume; } int ltop[maxn],fa[maxn],deep[maxn]; int sz[maxn],remp[maxn]; int son[maxn],cnt; void init(int n){rep(i,1,n) head[i]=0;cnt=0,nume=1;} void dfs1(int now=1,int pre=1,int d=0){ deep[now]=d,fa[now]=pre,sz[now]=1,son[now]=0; forn(i,now){ int to=e[i].to; if(to!=pre) { dfs1(to,now,d+1); sz[now]+=sz[to]; if(sz[to]>sz[son[now]]) son[now]=to; } } } void dfs2(int now=1,int pre=1,int sp=1){ ltop[now]=sp;mp[now]=++cnt;remp[cnt]=now; if(son[now]) dfs2(son[now],now,sp); forn(i,now){ int to=e[i].to; if(to!=son[now]&&to!=pre) dfs2(to,now,to); } } void getchain(){dfs1();dfs2();} int lca(int x,int y){ for(;ltop[x]!=ltop[y];deep[ltop[x]]>deep[ltop[y]]?x=fa[ltop[x]]:y=fa[ltop[y]]); return deep[x]<deep[y]?x:y; }//@基础部分@ void update(int a,int b,int val){ while(ltop[a]!=ltop[b]){ if(deep[ltop[a]]<deep[ltop[b]])swap(a,b); tree.update(mp[ltop[a]],mp[a],val); a=fa[ltop[a]]; } if(deep[a]>deep[b])swap(a,b); tree.update(mp[a],mp[b],val); } int query(int a,int b,int k){ int sum=0; while(ltop[a]!=ltop[b]){ if(deep[ltop[a]]<deep[ltop[b]])swap(a,b); sum+=tree.query(mp[ltop[a]],mp[a],k); a=fa[ltop[a]]; } if(deep[a]>deep[b])swap(a,b); sum+=tree.query(mp[a],mp[b],k); return sum; } }g;
而且,分清楚算法的不同区域是很重要的,例如基础线段树的板子:
class segtree{public: #define nd node[now] #define ndl node[now<<1] #define ndr node[now<<1|1] struct segnode{ int l,r,val,tag; void update(int x){val+=x,tag+=x;} }node[maxn<<2|3]; int n; void pushup(int now){nd.val=min(ndl.val,ndr.val);} void pushdown(int now){ if(nd.tag){ ndl.update(nd.tag); ndr.update(nd.tag); nd.tag=0; } } void maketree(int s,int t,int now=1){ nd={s,t,a[s]}; if(s==t) return ; maketree(s,(s+t)>>1,now<<1); maketree(((s+t)>>1)+1,t,now<<1|1); pushup(now); } void update(int s,int t,int x,int now=1){ if(s<=nd.l&&t>=nd.r) {nd.update(x);return ;} pushdown(now); if(s<ndr.l) update(s,t,x,now<<1); if(t>ndl.r) update(s,t,x,now<<1|1); pushup(now); } int query(int s,int t,int now=1){ if(s<=nd.l&&t>=nd.r) return nd.val;/ pushdown(now); int ret=1e9; if(s<ndr.l) ret=query(s,t,now<<1); if(t>ndl.r) ret=min(query(s,t,now<<1|1),ret); return ret; } }tree;
对于这份模板,修改成区间+,区间覆盖,区间01翻转等等都非常直接:改2个push,和node内的信息即可,而且想开几棵树就开几棵树
还有一些其他的代码习惯,比如
我所有的树状数据结构,板子里的类名虽然都不一样,比如线段树segtree,树状数组bit等等,
但是我板子里的变量名都是tree,并且接口也都是maketree,update,query等等,然后其他算法,比如树链剖分,也是如此
第二步,就是比赛的实际应用了
很多时候,你都会随时修改你的代码仓库,但是你经常又有比赛,难道每次比赛都去强行弄一个word吗?
显然不需要,考虑你现在已经有一个目录,分门别类的存好了所有的代码,
那么,如果有一个程序,可以每次一键帮我把代码拿出来组合成一个可以直接打印的pdf该多好?
答案是有的,那就是$latex$
是用内嵌代码文件功能,并且用目录索引快速定位!
1.每次添加入新的模板时,只需要在$latex$源修改添加subsection即可
2.每次改旧的模板时,完全不需要改$latex$代码
只需要在需要用的时候,编译一遍$latex$源代码就行
我的基本设置是这样的:
减小行距,段距,减小了页边距,增加了stl关键字+下划线,并使用横版a4大小,三栏,显示行号,制表符2格大小,
代码内嵌中文使用了逃逸符号,用\'@\'将cpp文件内的汉字包起来即可
并且最终双面打印,这样展开看的更多,比赛的时候可以不用经常翻页
不会latex? 学就是啦, 比python写脚本简单多了!
一劳永逸,latex一时爽,一直latex一直爽!
参考:
论文写作的又一利器:VSCode + Latex Workshop + MikTex
配置默认设置的时候,按[ctrl+\',\']进入设置,
然后在整体设置下面修改json,在大括号里加入上面的latex配置就行,注意json的语法,别忘了逗号..
初步的效果如下:
latex源代码如下,我自己非常满意,想用可以直接套我的设置,
用miktex+vscode可以顺利编译:
1 \\documentclass[UTF8]{ctexart} 2 \\usepackage{ctex} 3 \\usepackage{multicol} 4 \\usepackage[landscape,margin=10mm,bottom=5mm,left=5mm,right=5mm,headsep=2mm]{geometry} 5 \\usepackage[breaklinks,colorlinks,linkcolor=black,citecolor=black,urlcolor=black]{hyperref} 6 \\usepackage{graphicx} 7 \\usepackage[Glenn]{fncychap} 8 \\usepackage{listings} 9 \\usepackage{verbatim} 10 \\usepackage{CJKutf8} 11 \\usepackage{textcomp} 12 \\usepackage{xcolor} 13 \\usepackage{setspace} 14 \\usepackage{fancyhdr} 15 \\usepackage{titlesec} 16 \\usepackage{multicol} 17 \\usepackage{CJKutf8} 18 \\geometry{footskip=5mm} 19 \\title{ACM Standard Code Library} 20 \\author{Nervendings NWU-ACMICPC QQ:419000977} 21 \\definecolor{dkgreen}{rgb}{0,0.6,0} 22 \\definecolor{gray}{rgb}{0.5,0.5,0.5} 23 \\definecolor{mauve}{rgb}{0.58,0,0.82} 24 \\definecolor{green2}{rgb}{0,0.6,0.4} 25 \\definecolor{CPPLight} {html} {686868} 26 \\definecolor{CPPSteel} {HTML} {888888} 27 \\definecolor{CPPDark} {HTML} {262626} 28 \\definecolor{CPPBlue} {HTML} {4172A3} 29 \\definecolor{CPPGreen} {HTML} {487818} 30 \\definecolor{CPPBrown} {HTML} {A07040} 31 \\definecolor{CPPRed} {HTML} {AD4D3A} 32 \\definecolor{CPPViolet} {HTML} {7040A0} 33 \\definecolor{CPPGray} {HTML} {B8B8B8} 34 \\pagestyle{fancy} 35 \\lhead{CUMTB} 36 \\lhead{\\CJKfamily{hei} northwest university} 37 \\chead{} 38 \\rhead{Page \\thepage} 39 \\rhead{\\CJKfamily{hei} Page \\thepage} 40 \\lfoot{} 41 \\cfoot{} 42 \\rfoot{} 43 \\setlength{\\columnsep}{10pt} 44 \\renewcommand{\\headrulewidth}{0.4pt} 45 \\renewcommand{\\footrulewidth}{0.4pt} 46 \\setlength{\\parskip}{0pt} 47 \\setlength{\\lineskip}{1pt} 48 \\titlespacing{\\section} {0pt}{0pt}{0pt} 49 \\titlespacing{\\subsection} {0pt}{0pt}{2pt} 50 \\lstset{frame=tb, 51 language=c++, 52 aboveskip=0pt, 53 belowskip=0pt, 54 showstringspaces=false, 55 columns=flexible, 56 basicstyle={\\ttfamily}, 57 numbers=left, 58 numberstyle=\\small, 59 keywordstyle=\\color[RGB]{40,40,255}\\bfseries\\underbar, 60 commentstyle=\\it\\color[RGB]{0,96,96}, 61 stringstyle=\\rmfamily\\slshape\\color[RGB]{128,0,0}, 62 breaklines=true, 63 tabsize=2, 64 frame=single, 65 numberblanklines=false, 66 numbersep=5pt, 67 escapeinside=@@, 68 xleftmargin=5pt,xrightmargin=5pt, 69 framexleftmargin=1pt,framexrightmargin=0pt, 70 captionpos=t, 71 breaklines=true, 72 morekeywords={alignas,continute,friend,register,true,alignof,decltype,goto, 73 reinterpret_cast,try,asm,defult,if,return,typedef,auto,delete,inline,short, 74 typeid,bool,do,int,signed,typename,break,double,long,sizeof,union,case, 75 dynamic_cast,mutable,static,unsigned,catch,else,namespace,static_assert,using, 76 char,enum,new,static_cast,virtual,char16_t,char32_t,explict,noexcept,struct, 77 void,export,nullptr,switch,volatile,class,extern,operator,template,wchar_t, 78 const,false,private,this,while,constexpr,float,protected,thread_local, 79 const_cast,for,public,throw,std}, 80 emph={map,set,multimap,multiset,unordered_map,unordered_set, 81 unordered_multiset,unordered_multimap,vector,string,list,deque, 82 array,stack,forwared_list,iostream,memory,shared_ptr,unique_ptr, 83 random,bitset,ostream,istream,cout,cin,endl,move,default_random_engine, 84 uniform_int_distribution,iterator,algorithm,functional,bing,numeric,}, 85 emphstyle=\\color{CPPViolet}\\underbar, 86 } 87 \\begin{document} 88 \\begin{CJK}{UTF8}{<font>} 89 \\begin{multicols}{3} 90 \\begin{spacing}{0.80} 91 \\begin{titlepage} 92 \\maketitle 93 \\thispagestyle{empty} 94 \\pagebreak 95 \\pagestyle{plain} 96 \\tableofcontents 97 \\end{titlepage} 98 \\section{DataStruct} 99 \\subsection{动态开点线段树} 100 \\lstinputlisting{DataStruct/dynamic-segtree-1.cpp} 101 \\subsection{区间第k大} 102 \\lstinputlisting{DataStruct/kth.cpp} 103 \\subsection{线段树优化建图} 104 \\lstinputlisting{DataStruct/segtree-graph.cpp} 105 \\subsection{线段树合并} 106 \\lstinputlisting{DataStruct/segtree-merge.cpp} 107 \\subsection{二维线段树} 108 \\lstinputlisting{DataStruct/xysegtree.cpp} 109 \\subsection{主席树} 110 \\lstinputlisting{DataStruct/psegtree.cpp} 111 \\subsection{Splay} 112 \\lstinputlisting{DataStruct/SplayTree.cpp} 113 \\subsection{Treap} 114 \\lstinputlisting{DataStruct/treap.cpp} 115 \\subsection{老司机树} 116 \\lstinputlisting{DataStruct/ODT.cpp} 117 \\subsection{可持久化01字典树} 118 \\lstinputlisting{DataStruct/p-01trie.cpp} 119 \\subsection{并查集非递归+按秩合并} 120 \\lstinputlisting{DataStruct/ufs-2.cpp} 121 \\subsection{并查集可删除版} 122 \\lstinputlisting{DataStruct/ufs.cpp} 123 \\subsection{ST表} 124 \\lstinputlisting{DataStruct/s-table.cpp} 125 \\subsection{扫描线算法} 126 \\lstinputlisting{DataStruct/scanline-algorithm.cpp} 127 \\subsection{树状数组} 128 \\lstinputlisting{DataStruct/bit.cpp} 129 \\section{Graph} 130 \\subsection{最大流Dinic算法} 131 \\lstinputlisting{Graph/dinic.cpp} 132 \\subsection{费用流原始对偶算法} 133 \\lstinputlisting{Graph/mncf-dij.cpp} 134 \\subsection{费用流EK+spfa算法} 135 \\lstinputlisting{Graph/mncf-spfa.cpp} 136 \\subsection{Tarjan 求桥} 137 \\lstinputlisting{Graph/tarjan-bridge.cpp} 138 \\subsection{Tarjan 求割顶} 139 \\lstinputlisting{Graph/tarjan-point.cpp} 140 \\subsection{Dijkstra最短路} 141 \\lstinputlisting{Graph/dij.cpp} 142 \\subsection{重链剖分} 143 \\lstinputlisting{Graph/Heavy-chain-subdivision-edge.cpp} 144 \\subsection{欧拉路径} 145 \\lstinputlisting{Graph/euler-path.cpp} 146 \\subsection{三元环计数} 147 \\lstinputlisting{Graph/3-circle.cpp} 148 \\subsection{最小生成树Prim算法} 149 \\lstinputlisting{Graph/prim.cpp} 150 \\subsection{SW全局最小割算法} 151 \\lstinputlisting{Graph/Stoer-Wagner.cpp} 152 \\subsection{树上点分治} 153 \\lstinputlisting{Graph/tree-v-div.cpp} 154 \\section{Geometry} 155 \\subsection{计算几何} 156 \\lstinputlisting{Geometry/basic.cpp} 157 \\section{Math} 158 \\subsection{逆元,快速幂,快速乘} 159 \\lstinputlisting{Math/math-bass.cpp} 160 \\subsection{欧拉函数} 161 \\lstinputlisting{Math/euler_phi.cpp} 162 \\subsection{欧拉函数2} 163 \\lstinputlisting{Math/euler.cpp} 164 \\subsection{莫比乌斯函数} 165 \\lstinputlisting{Math/mobius.cpp} 166 \\subsection{米勒罗宾素数判定} 167 \\lstinputlisting{Math/Miller-Rabin.cpp} 168 \\subsection{大整数因子分解} 169 \\lstinputlisting{Math/Pollard-Rho.cpp} 170 \\subsection{拉格朗日插值} 171 \\lstinputlisting{Math/polysum.cpp} 172 \\subsection{异或线性基} 173 \\lstinputlisting{Math/linear-bass.cpp} 174 \\subsection{线段树维护区间线性基} 175 \\lstinputlisting{Math/segtree-linearbass.cpp} 176 \\section{String} 177 \\subsection{KMP} 178 \\lstinputlisting{String/kmp.cpp} 179 \\section{Others} 180 \\subsection{快速读写} 181 \\lstinputlisting{Others/fastio.cpp} 182 \\subsection{LIS} 183 \\lstinputlisting{Others/lis.cpp} 184 \\subsection{矩阵} 185 \\lstinputlisting{Others/matrix_pow.cpp} 186 \\subsection{头文件} 187 \\lstinputlisting{Others/head.cpp} 188 \\subsection{莫队算法} 189 \\lstinputlisting{Others/mo-algorithm.cpp} 190 \\end{spacing} 191 \\end{multicols} 192 \\end{CJK} 193 \\end{document}
但是每次做模板都要很麻烦的复制粘贴,所以可以考虑更方便的做法,
那就是写一个脚本,直接抓取同文件夹下的源代码,组合成一个latex源代码文件
这里提供一个简单的参考实现
head为默认的环境配置,tail为默认的配置结束部分
config即为各个section的内容,每次generator在同目录下抓取config,head,tail,读取配置信息,然后生成一个tex源文件,
最后编译一下就可以了
generator源代码:
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;++i) using namespace std; const int maxn=1000,maxm=100; const char *index_name = "/config"; char section[maxm][maxn],temp[maxn]; char section_name[maxn],file_name[maxn]; int name_len,fill_len,count_secn; FILE* tex_source = NULL; void get_section(const char *s) { name_len=fill_len=0; int pos=0; while(s[pos]&&s[pos]!=\'<\') ++pos; if(s[pos]) ++pos; while(s[pos]&&s[pos]!=\'>\') section_name[name_len++]=s[pos++]; while(s[pos]&&s[pos]!=\'<\') ++pos; if(s[pos]) ++pos; while(s[pos]&&s[pos]!=\'>\') file_name[fill_len++]=s[pos++]; section_name[name_len]=file_name[fill_len]=0; } void read_dir(){ FILE* dir=fopen(index_name+1,"r"); count_secn=0; while(fgets(section[count_secn++],maxn,dir)!=NULL); fclose(dir); while(section[count_secn-1][0]==\'\\0\'&&count_secn>0) --count_secn; rep(i,0,count_secn-1) if(section[i][strlen(section[i])-1]!=\'\\0\') section[i][strlen(section[i])-1]=\'\\0\'; } void put_file(const char *filename){ FILE *fp=fopen(filename,"r"); while(fgets(temp,maxn,fp)!=NULL)fprintf(tex_source,temp); fclose(fp); } signed main() { auto start_time=chrono::high_resolution_clock::now(); tex_source=fopen("ICPC-StdLibrary.tex","w"); put_file("head.in");read_dir(); printf("section number:%d\\n",count_secn); rep(i,0,count_secn-1){ fprintf(tex_source,"\\\\section{%s}\\n",section[i]); memcpy(temp,section[i], sizeof temp); strcat(temp,index_name); printf("section-%d {%s}:\\n",i+1,section[i]); FILE* fp=fopen(temp,"r"); int cnt=0; while(fgets(temp,maxn,fp)!=NULL){ if(temp[0]!=\'<\')break; get_section(temp); printf("subsection-%d {%s}:",++cnt,file_name); fprintf(tex_source,"\\\\subsection{%s} ",section_name); memcpy(temp, section[i], sizeof temp); temp[strlen(temp)+1] = 0; temp[strlen(temp)] = \'/\'; strcat(temp,file_name); fprintf(tex_source,"\\\\lstinputlisting{%s}\\n",temp); puts(" Done"); } printf("section-%d {%s}: Done\\n",i+1,section[i]); fclose(fp); } put_file("tail.in");fclose(tex_source); puts("Well Done"); auto end_time=chrono::high_resolution_clock::now(); cout<<"\\nrun time: "<<chrono::duration<double,milli>(end_time-start_time).count()<<" ms\\n"; getchar(); }
以上是关于ACM选手进阶指北:一个好的代码库与latex维护代码文档的主要内容,如果未能解决你的问题,请参考以下文章