清北第二天,感受到了来自这个世界的不友善,大概把没听过不会的“名词”记录下来就已经一面了,然后被大佬说这都是最基础的东西,就很皮,那就趁别人练习字符串的题的时候,来写波博客了,倒不是不想写,(MD这么快就KMP,Hash,Manacher,AC自动机,Tire树,我毛都不会啊没法写啊,讲的巨难,详细也听不懂啊,上来一个指针就蒙了)
不扯没意思的,从早上开始积累的问题慢慢总结吧。
1、倍增:第一次听到这个词是懵逼的,然后什么树上倍增的直接让我就傻逼了,实际上在之前有次上课的时候,老师提了一句话,现在想起来倒是发现体现了倍增的思想,倍增倍增,成倍增加,就是在算Nm的时候,可以直接把m拆成2*4*8*16*32*……,那么这个2 4 8 16 32 这一连串就体现了倍增的思想,加快了化学反应速率算法的的速度,提高效率,避免了不必要的重复计算。
2、树:可谓有各种奇葩的不奇葩的麻烦的难的树,今天讲的大概有如下几个几百个:生成树、最大生成树、最小生成树、LCA最近公共祖先、线段树、树状数组、树上倍增、有根树、重构树、虚树、树剖等……
一个一个来:
生成树:如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树(SpanningTree)。生成树是连通图的包含图中的所有顶点的极小连通子图。
最小生成树:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。即最小权重生成树,每个边所带的权重值的和最小即为最小生成树。
严格次小生成树:
最大生成树:与最小生成树相对,每个边所带的权重值和最大即为最大生成树。
线段树:数据结构中的一种,是一种二叉搜索树,与区间树相似。
树状数组:是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
#include<iostream> using namespace std; ],t[],l,r;//num:原数组;t:树状数组 int lowbit(int x) { return x&(-x); } void change(int x,int p)//将第x个数加p { while(x<=n) { t[x]+=p; x+=lowbit(x); } return; } int sum(int k)//前k个数的和 { ; ) { ans+=t[k]; k-=lowbit(k); } return ans; } int ask(int l,int r)//求l-r区间和 { ); } int main() { cin>>n>>m; ;i<=n;i++) { cin>>num[i]; change(i,num[i]); } ;i<=m;i++) { cin>>l>>r; cout<<ask(l,r)<<endl; } ; }百科的
重构树:由最小生成树产生的,在使用kruskal算法考虑最小生成树的时候,树的结构和边发生了一定的改变,这里我们就把他叫做重构树,重新构建了一个树,故其边的权重值也发生了改变,那么既然提到了克鲁斯卡尔算法,就再来百度一波
kruskal算法:先对所有的边进行排序,循环地从权值最小的边开始遍历每条边 直至图Graph中所有的节点都在同一个连通分量中 if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中添加这条边到图Graphnew中。最后构成的这个数就是最小生成树。
同理还有Prime算法:任取一个点作为树的起点,该点引出的所有边中,先选出最小的一条,然后得到一个新的节点,从这两个点中引出的边中,选出最小的一条,以此类推。
即重复地操作,直到Vnew = V:a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);b.将v加入集合Vnew中,将<u, v>边加入集合Enew中。
虚树:不存在的树,把需要询问的点放到另一个树上,即使不是一样的树。http://blog.****.net/jzhang1/article/details/50535800(安利一下某个大佬的网站)
LCA+树上倍增:最近公共祖先,Least common ancestor,对于这个图而言,5 的父亲节点为2,父亲的父亲节点为1,4的父亲节点为3,父亲的父亲节点为2,父亲的……的节点为1,那么4、5最近的公共祖先就是2。
那么很容易想到一个暴力算法,就是DFS搜出每个点的深度,再不停地往上跳,那就会很浪费时间,所以结合第一个倍增的思想来看,就有了树上倍增的说法,所以代码如下。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> using namespace std; vector <]; ][]={}; ]={}; int n,m; ]={false}; int root; void dfs(int u) { int i; visit[u]=true; ;i<g[u].size();i++) { int v=g[u][i]; if ( !visit[v] ) { depth[v]=depth[u]+; dfs(v); } } }//深搜出各点的深度,存在depth中 void bz() { int i,j; ;j<=;j++) ;i<=n;i++) father[i][j]=father[father[i][j-]][j-]; }//倍增,处理father数组,详情参照上述讲解 int LCA(int u,int v) { if ( depth[u]<depth[v] ) { int temp=u; u=v; v=temp; }//保证深度大的点为u,方便操作 int dc=depth[u]-depth[v]; int i; ;i<;i++)//值得注意的是,这里需要从零枚举 { <<i) & dc)//一个判断,模拟一下就会很清晰 u=father[u][i]; } //上述操作先处理较深的结点,使两点深度一致 if (u==v) return u;//如果深度一样时,两个点相同,直接返回 ;i>=;i--) { if (father[u][i]!=father[v][i])//跳2^j步不一样,就跳,否则不跳 { u=father[u][i]; v=father[v][i]; } } u=father[u][];//上述过程做完,两点都在LCA下一层,所以走一步即可 return u; } int main() { int i,j; scanf("%d",&n); ;i<=n;i++) g[i].clear(); ;i<n;i++) { int a,b; int root; scanf("%d%d",&a,&b); g[a].push_back(b); father[b][]=a; ]==) root=a; } depth[root]=; dfs(root); bz(); int x,y; scanf("%d%d",&x,&y); printf("%d",LCA(x,y)); ; }
3.图:之前的各种树应该建立在图的基础上,毕竟树也是图的一种嘛,所以也就有了一下几个名词:无向图,有向图,并查集等……