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一直爽!

参考:

LaTeX快速入门指南

论文写作的又一利器:VSCode + Latex Workshop + MikTex

论文杀器LaTeX用法汇总(3)——实战·ACM代码模板

如何安装MiKTeX

配置VSCode 作为LaTex 编辑器

Latex中文utf-8编码的三种方式

Latex插入代码并高亮显示

配置默认设置的时候,按[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}
View Code

 

但是每次做模板都要很麻烦的复制粘贴,所以可以考虑更方便的做法,

那就是写一个脚本,直接抓取同文件夹下的源代码,组合成一个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();
}
posted @ 2019-05-24 00:54  nervending  阅读(514)  评论(0编辑  收藏  举报