【★】算法目录 Algorithm Summary
【算法】
————【专题】生成树计数(矩阵树定理)
————【算法专题】卡特兰数(计数数列)
————【专题】数论
————【专题】概率和期望
————【算法专题】后缀自动机SAM
一个总结性博客:Arya__
【开始模板】
标准模板
#include<cstdio> #include<cstring> #include<cctype> #include<cmath> #include<queue> #include<stack> #include<set> #include<vector> #include<algorithm> #define ll long long #define lowbit(x) x&-x using namespace std; int read(){ char c;int s=0,t=1; while(!isdigit(c=getchar()))if(c=='-')t=-1; do{s=s*10+c-'0';}while(isdigit(c=getchar())); return s*t; } int min(int a,int b){return a<b?a:b;} int max(int a,int b){return a<b?b:a;} int ab(int x){return x>0?x:-x;} //int MO(int x){return x>=MOD?x-MOD:x;} //void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;} /*------------------------------------------------------------*/ const int inf=0x3f3f3f3f; int n; int main(){ return 0; }
fread(空间)
#include<cstdio> char buf[10000010],*ptr=buf-1; int read()//48~57 { char c=*++ptr;int s=0,t=1; while(c<48||c>57){if(c=='-')t=-1;c=*++ptr;} while(c>=48&&c<=57){s=s*10+c-'0';c=*++ptr;} return s*t; } int main() { fread(buf,1,sizeof(buf),stdin); return 0; }
fread(缓存)
const int M=1e5; char ib[M+7],*ip=ib+M; int G(){ if(ip==ib+M)fread(ip=ib,1,M,stdin)[ib]=0; return *ip++; } int read(){ int x=0,f=1; if(ip<ib+M-100){ while(*ip<48)*ip++=='-'?f=-1:0; while(*ip>47)x=x*10+*ip++-48; }else{ int c=G(); while(c<48)c=='-'?f=-1:0,c=G(); while(c>47)x=x*10+c-48,c=G(); } return x*f; }
【随机/对拍/Linux】
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int seed=123456789;//?? int rnd() {return seed=(seed*1844677ll+1234577)%1000000007;}//%10^9+7 int a[1000010]; void build(int n){ for(int i=1;i<=n;i++)a[i]=i; for(int i=1;i<=n;i++){ int x=rnd()%(n-i+1)+i; swap(a[i],a[x]); } } int main() { for(int T=0;;T++) { FILE*f=fopen("in.txt","w"); /*----------????----------*/ int n=rnd()%100+1; fprintf(f,"%d\n",n); /*----------------------------*/ fclose(f);//Important printf("---%d---\n",T); system("cyc1.exe<in.txt>o1");//bat?? system("cyc2.exe<in.txt>o2"); system("fc o1 o2||pause");//linnux?fc??diff pause??./pause ??pause.cpp???{getchar();return 0;}? } //windows????:tasklist taskkill -f -im sth.exe //linux????:killall GUIDE ps -ef return 0; }
Linux常用指令:
编译:g++ cyc.cpp -o cyc -O2 -Wall(将cyc.cpp编译后生成可执行文件cyc)
运行:./cyc
显示路径:pwd
进入文件夹:cd CYC(返回上一层:cd ..)
杀进程:killall GUIDE
进程列表:ps -ef
Linux对拍:(另写并编译文件pause,内容为{getchar();return 0;})
#include<cstdio> #include<algorithm> using namespace std; int seed; int rnd(){return seed=(seed*1844677ll+1234577)%1000000007;} int main(){ for(int T=0;;T++){ FILE*f=fopen("in.txt","w"); int n=rnd()-500000000; fprintf(f,"%d",n); fclose(f); printf("-------%d--------\n",T); system("./cyc<in.txt>o1"); system("./cyc2<in.txt>o2"); system("diff o1 o2||./pause"); } return 0; }
【RMQ】
void RMQ_init() { for(int i=1;i<=n;i++)d[i][0]=a[i]; for(int j=1;(1<<j)<=n;j++) for(int i=1;i+(1<<j)-1<=n;i++) d[i][j]=min(d[i][j-1],d[i+(1<<(j-1))][j-1]); } int RMQ(int L,int R) { int k=0; while((1<<(k+1))<=R-L+1)k++; return min(d[L][k],d[R-(1<<k)+1][k]); }
【离散化】
scanf("%d",&n); for(i=1;i<=n;i++) scanf("%d",&e[i]),w[++lw]=e[i]; sort(w+1,w+lw+1),lw=unique(w+1,w+lw+1)-w-1; for(i=1;i<=n;i++) e[i]=lower_bound(w+1,w+lw+1,e[i])-w;
【STL】
【STL-set/multiset】
set是集合,multiset才是可重复集合。
set左闭右开,s.begin()返回第一位地址,s.end()返回最后一位之后一位的地址(存储的键值不确定)
直接调用set得到地址,需要[*it](int)或[it->x](struct)转换为键值。
set<int>::iterator it; 定义一个可以存储地址的迭代器,迭代即只能it++或it--。
s.lower_bound()
s.upper_bound()
s.find()
s.erase(k)删除键值为k的元素(也可删除地址),注意在multiset中必须s.erase(s.find(k))才能只删除一个元素。
【STL-优先队列】
(priority_queue)
代码为int重载,结构体重载见下面。
#include<cstdio> #include<algorithm> #include<queue> using namespace std; priority_queue<int>big;//大顶堆 priority_queue<int,vector<int>,greater<int> >small;//小顶堆 struct cmp//重载为从大到小 { bool operator() (const int a,const int b) const {return a<b;}//返回真说明要交换,这与sort中返回真说明不交换是相反的 };//要有分号! priority_queue<int,vector<int>,cmp>q; int main() { int n,u; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&u); q.push(u); } printf("size=%d\n",q.size()); while(!q.empty()) { printf("%d ",q.top()); q.pop(); } printf("\n"); return 0; }
【STL-vector】动态数组
vector之间可以直接赋值或者作为函数的返回值。
s.push_back(x)
s.pop_back(x)
s[x]
没开O2下速度很慢,慎用!
#include<vector> vector<int>p[maxn]; p[0].clear(); p[0].push_back(x);//从0开始! p[0].pop_back(x); if(!p[0].empty);
【STL-bitset】
用于资瓷长二进制。
bitset<100>s;定义s为100位二进制数。
count计算1的数量。
s=p直接将整数p的二进制赋值给s。
bitset<500>a(0),b(0);//括号赋初值 int number=s.count();//计算1的数量 int number=s.size();//大小 s=0;//整体赋值 s[0]=0; a=b&c;a=b^c;
【STL-permutation】全排列
全排列:指n个数字任意排列,出现的不同数列。
下一个全排列:将所有全排列按照字典序排序后,下一个全排列。
只需要定义比较函数就可以实现全排列,不关注数组内的数字是否相同等。
next_permutation(begin,end),有下一个则返回1并改变数组,否则返回0。
prev_permutaion()同理,上一个排列。
#include<cstdio> #include<algorithm> using namespace std; const int maxn=10; int a[maxn],n; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&a[i]); do{ for(int j=1;j<=n;j++)printf("%d ",a[j]);puts(""); }while(next_permutation(a+1,a+n+1)); return 0; }
【STL-map】
映射,常用于数组太大开不下,或建立string到int的映射。
map<int,int>s;(第一个相当于下标)
s.count(x)判断下标x是否存在
s[x]=y;赋值
s.erase(x)删除下标x
s.lower_bound(x)
两个元素是first和second
#include<cstdio> #include<algorithm> #include<cstring> #include<map> using namespace std; int n,a[100]; map<int,int>s; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); s.insert(pair<int,int>{i+1,a[i]}); } s.erase(3); for(int i=2;i<=n+1;i++)printf("%d\n",s[i]); return 0; }
【STL-string】
长度:size()
输入输出:cin>>,cout<<
比较:字典序(常放进map)
拼接:s=s+'x'
【系统时间】
clock()返回long long(不一定表示毫秒),CLOCKS_PER_SEC值为long long,所以要记得*1.0转double。
1.0*clock()/CLOCKS_PER_SEC单位转为s,类型double,注意*1.0
int t0=clock(); //work... int t1=clock(); printf("%d\n",t1-t0); double t2=1.0*clock()/CLOCKS_PER_SEC; //work... double t3=1.0*clock()/CLOCKS_PER_SEC; printf("%.1lf\n",t3-t2);
【重载】
在结构体中重载运算符。
重载“<”时,priority_queue恰好相反。
好像重载非比较运算符时后面不加const。
struct cyc{ int num,ord; // cyc(){num=0;}//默认初始化 加上后不能直接赋值 bool operator < (const cyc &x) const {return num<x.num;}//重载运算符 }a[100]; a[++n]=(cyc){0,0};//直接赋值
【二分】
二分答案:构造问题-->判定问题
★遵循左优先原则,即和左比较。
while(l<r) { int mid=(l+r)>>1; if()l=mid+1;else r=mid; //靠左就是最左,靠右就是最右+1 }
是否有等号只是决定要找哪个数字区间。
l=mid+1 r=mid
★会寻找到对应数字区间的偏大区间第一个数字。
这也只是一个在区间临界点的与左比较后作出的决策区别而已。
这种方式有边界问题:即如果序列最后一个数字满足要求,但实际上l-1就无法对应答案。
★解决办法是初始r+1,根据左优先原则这个r+1不可能被访问,但可以解决边界问题。
【三分】
初始左右端点l,r
while
左右端点距离≤5时换成暴力。
x1=l+(r-l)/3,x2=l+(r-l)/3*2;
y1=calc(x1),y2=calc(x2);
★更新更劣一侧端点。(优点可能和劣点同侧,劣点只有一种可能性)
end.
复杂度O(log3n)。
注意,三分依赖于两个三分点的大小比较确定最优解方位,故
如果函数存在平段就不能三分。(除非平段在答案处)
当然也有解决方法,如果整个区间平段就是答案,否则每次任取两个点,如果平段就再取直到不平段为止。或者多换一下初始三分点等。
【二进制原理】
1...k中的数可以由1.2.4...2t.k-2t+1+1(2t+2 > k ≥ 2t+1)组成。
(若1...5,则1.2就可以组成1..3,然后再加个2就可以由2+2,3+2得到4.5。)
原理就是1~$2^t$就可以构成1~$2^{t+1}-1$了,补个差值$k-2^{t+1}+1$就行了。
实现:枚举基数指数k,要分解数字为num,不断num-=k,直到num剩余的部分不再足以支撑一个基数2k时,把剩余的num加进去即可。
从二进制表示理解,由于111+111=1110,所以这种做法的实质是:找到最大的111<=x,再加上附加数x-111。
【哈希】
哈希的主要目的一般都是判重,例如叠加给同样的数字一个附加值,给同样的字符串附加信息。对于一个数字和一个数组,判断相同要全部扫一遍数组,时间上承受不起,或者建成一对一判定的数组,空间上又承受不起,所以找到折中方案:哈希,把数组取模然后做成一些链。
哈希空间换时间(和邻接表一样把真实情况堆进一个数组,用头表记录位置):MOD一般开到点数三倍大小。
<1>first[MOD](头表) <2>tot(总数) <3>e[STATE].from(上一个)state(真实值)num(附带信息)
这三者也是构成邻接表的核心,对于邻接表MOD换成点数,STATE换成边数,再改一下记录的信息而已。邻接表是一个点的邻边组成一条链,哈希表是一个哈希值对应的真实值组成一条链。
操作包括:1.取指定数字的附加值:取模后去对应链找匹配真实值的数字;2.存入指定数字的附加值:取模后去对应链找匹配真实值的数字;3.遍历:直接遍历杂乱的原值数组,这时哈希只是在插入时起了判重作用;4.清空:清空头表first和tot,只有这个操作跟复杂度与MOD大小有关。不过有一种方法:记录时间戳,每次检测到旧时间戳就更新之。
struct HASHMAP{ int first[MOD],tot,from[STATE],state[STATE],f[STATE]; void init()//清空 { tot=0; memset(first,0,sizeof(first)); } void push(int number,int num)//插入 { int x=number%MOD; for(int i=first[x];i;i=from[i]) if(number==state[i]) { f[i]=min(f[i],num); return;//已有,不用加入,直接return } tot++; state[tot]=number; f[tot]=num; from[tot]=first[x]; first[x]=tot; } };
字符串哈希见【算法】字符串。
双hash,其中一个用自然溢出,好像还不错。
【高级搜索方法】
启发式搜索(A*算法)
只考虑起点出发的dijkstra算法,每次将点集中距离起点最短的点加入考虑,到达终点为止。
只考虑终点启发的BFS算法,每次将距离终点估价最短的点加入考虑,到达终点为止。
启发式函数f(n)=h(n)+g(n),g(n)表示从初始结点到任意结点n的代价,h(n)表示从结点n到目标点的启发式评估代价
启发式函数可以控制A*的行为:
1.如果h(n)是0,则只有g(n)起作用,此时A*演变成Dijkstra算法,这保证能找到最短路径。
2.如果h(n)经常都比从n移动到目标的实际代价小(或者相等),则A*保证能找到一条最短路径。h(n)越小,A*扩展的结点越多,运行就得越慢。
解释:h(n)较小时,终点的启发性较差,结点就会四处拓展,速度较慢。h(n)比实际小就不可能影响最短路径的决策。
3.如果h(n)精确地等于从n移动到目标的代价,则A*将会仅仅寻找最佳路径而不扩展别的任何结点。这就是A*求解第k短路的原理。
4.如果h(n)有时比从n移动到目标的实际代价高,则A*不能保证找到一条最短路径,但它运行得更快。
解释:h(n)较大时,终点启发性很强,结点会趋向终点方向拓展。
5.如果h(n)比g(n)大很多,则只有h(n)起作用,A*演变成BFS算法。
A*的核心是估价函数的设计——结合启发式函数和常规函数。
迭代加深搜索(ID算法)
从小到大枚举深度上限maxd,每次执行只考虑深度不超过maxd的结点,相当于强行规定解的深度避免无限度搜索。
只要解的深度有限,就一定能在有限时间枚举到。
IDA*算法(ID+A*)
在迭代加深搜索的基础上,设当前深度为g(n),乐观估价函数为h(n),如果g(n)+h(n)>maxd时应该剪枝,这就是IDA*算法。
【其它算法】
【分数规划】转换为与0的判定问题
【搜索】
剪枝:可行性剪枝,最优性剪枝
可行性剪枝:奇偶性剪枝、剩余和不可达剪枝
【倍增】每次变化规则必须相同并满足结合律!
【折半搜索】Meet-in-the-middle思想的一些应用
寻找a+b+c+d=0,转化为a+b=-(c+d),一边哈希一边枚举。
对于n的选择,分成两个值数组,分别存储前后n/2的数字组合出来的数字,然后两个指针(从大到小和从小到大)顺序考虑合并。
【暴力DFS】
dfs括号内存对应参数。
哈希:排序后做成base进制数判重。
【进制转换】
十进制转二进制 while n>0 do begin inc(num); a[num]=n mod 2; n:=n div 2; end; 二进制转十进制 now=1 for i=1 to num do begin ans:=ans+a[i]*now; now:=now*2; end;
【后缀表达式】
【高精度乘法(high*low)】
x=0; for(int i=1;i<=lens;i++) { x+=sum[i]*num; sum[i]=x%10; x/=10; } while(x>0)sum[++lens]=x%10,x/=10;
【高精度除法(high/low)】
【字符串哈希】
const int MOD=100000007; int Hash(char ch[]) { int s=0; for(int i=0;i<strlen(ch);i++) {s*=27;s+=(ch[i]-'a'+1);s%=MOD;} return s; }
【贪心】【BZOJ】1828: [Usaco2010 Mar]balloc 农场分配(经典贪心)
【指针】
int *a; 定义指向int的指针a
*a 取地址的变量
a-->b 取地址的结构体变量
&a 取变量的地址
【基数排序】
对于每一段:
①memset
②桶加A
③指针数组指向新数组B
④赋值给指针数组A
⑤swap
namespace Qsort{ int *s[300],a[maxn],b[maxn],T[300]; void qsort(int *a,int n){ int *A=a,*B=b,*mp; int mx=256; for(int k=0;k<4;k++){ memset(T,0,sizeof(T));mp=B; for(int i=0;i<n;i++)T[A[i]>>(8*k)&255]++; for(int i=0;i<mx;i++)s[i]=mp,mp+=T[i]; for(int i=0;i<n;i++)*s[A[i]>>(8*k)&255]++=A[i]; swap(A,B); } } }
【RMQ-LCA】例题: 树上的最远点对
dfs序:每个点的入栈出栈序,可以选择记录出栈点或不记录。(方便查询子树,如果记录入栈+1出栈-1则可以维护点到根的路径:星系探索)
欧拉序:中序遍历,每次遍历到就记一次。
欧拉序求LCA:两点区间的深度最小值,用RMQ求解。
欧拉序求子树权值和:查询该点在欧拉序中最后出现的位置的前缀减去第一次出现的位置-1的前缀和。
【哈夫曼树】Huffman Tree
哈夫曼树又称最优构造树,是使树的带点权路径长度和最小的树的形态。
带点权路径(WPL)=点权*深度。
构造方法:每次取点权最小的两个根节点作为左右子树(左小右大)组成新根节(点权为左右之和),多次操作直到只剩一棵树。(类似合并果子)
哈夫曼编码:从根出发左0右1,走到每个字符的二进制就是它的编码。
Huffman Code是电文总长最短的二进制编码方式,将n个字符的出现频率作为点权构造哈夫曼树得到的就是对应的哈夫曼编码。
例题:【CodeForces】700 D. Huffman Coding on Segment 哈夫曼树+莫队+分块