目录(按字典序)

A

——A*

D

——DFS找环

J

——基环树

S

——数位动规

——树形动规

T

——Tarjan(e-DCC)

——Tarjan(LCA)

——Tarjan(SCC)

——Tarjan(v-DCC)


A*

用处

当你在做搜索题时,发现各种剪枝的效果都不怎么好,那也就意味着你在搜索时将遇到一棵庞大的搜索树。根据广度优先搜索的性质,当第一次搜索到答案时就必定是最优解,所以在求解最优解一类的问题时我们唯一的策略就是让程序快点搜到答案,也就是尽可能往靠近答案的地方搜索。这里就要用到A*

思想

"未来预估"。先得出队列中所有状态的预估值,也就是离答案还有多远。然后用优先队列维护出从起始状态到当前状态的值+预估值最小的状态,优先从它开始拓展。

算法流程

设计出预估函数。预估函数的要求是:预估值≤实际值。然后广度优先搜索,当算出当前状态的代价时,计算出它的预估值,把代价+预估值的值加入优先队列。反复拓展直到搜出答案。

核心代码

inline int f(......){ ......; return; }//自己设计的预估函数

priority_queue< node > q;//自己设计出存储状态和优先级的结构体
inline int bfs(){
q.push(初始state);
while(q.size()){
node now=q.front(); q.pop();
if(now==ans) return ......;
if(vis[now.(......)]) continue;
for(......){
......(得出新状态);
q.push(node(新状态,新状态的代价+预估值));
}
}
}

时间复杂度为O(搜索分叉数^搜索树规模),但实际远远达不到这个程度


DFS找环

用处

当你做基环树的题时好不容易有了思路,却发现不会找环就很尴尬。这时候可以用到DFS找环的方法

思想

有向图

类似于Tarjan求强连通分量的方法,只不过不需要用到时间戳和追溯值之类的高级东西。

无向图

类似于Tarjan求点双连通分量的方法,要用到时间戳,但不需要追溯值。

算法流程

有向图

维护一个栈,把遍历到的点入栈。当发现下一个点被遍历过且这个点在栈中,那么找到了环,不断弹出栈顶直到下一个点出栈为止,出栈的点共同构成了一个环。否则往下一个点遍历。最后回溯时把当前点出栈。

无向图

当遍历到一个点u时,给这个点打上时间戳。遍历过程中记录下每个点在搜索树上的父亲。当发现下一个点v被遍历过,并且dfn[u]<dfn[v],那么先把v加入环中,然后不断把fa[v]加入环中并让v成为fa[v],直到u也被加入环中。

核心代码

有向图

int stack[maxn],top;
bool vis[maxn],instack[maxn];
void dfs(int u){
vis[u]=instack[u]=true,stack[++top]=u;
for(register int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(!vis[v]) dfs(v);
else if(instack[v]){
int w,t=top;
do{
w=stack[t--],instack[w]=false;
......(有关环的操作);
}while(w!=v);
}
}
instack[u]=false,top--;
}

无向图

void dfs(int u){
dfn[u]=++tot;
for(register int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(v==fa[u]) continue;
if(!dfn[v]) fa[v]=u,dfs(v);
else if(dfn[u]<dfn[v]){
......(环中关于v的操作)
do{
......(环中关于fa[v]的操作);
v=fa[v];
}while(v!=u);
}
}
}

时间复杂度都是O(N)


基环树

用处

如果你把树上的一些问题做得很熟练了,请不要狂妄。因为如果给树加上一条边,题目的算法并没有变,但难度确噌噌上去了。此时就要用到基环树的一些做法和性质来求解

思想

做基环树的题一般会先求解断开环上所有边之后每棵子树的答案,再加上环上部分。

核心代码

基环树的题很灵活,随机应变吧~

时间复杂度为O(N(找环) + 处理子树的复杂度 + 处理环的复杂度)


数位动规

用处

解决关于数的统计类的问题。一般情况下题目中有“求'一段区间内'满足'某个性质'的数的个数”时,就可以用数位动规来解。

思想

递推或者记忆化搜索,通过低位来更新高位。

核心代码:

int bit[];//原数
dfs(int len,state,bool havelim){//分别为:当前在第几位,前几位的状态,当前是否有限制
if(len==0) return (是否满足条件);
if(!havelim&&dp[len][state]) return dp[len][state];//记忆化
int lim=havelim?bit[len]:9; long long cnt=0;//多少个满足条件的数
for(register int i=0;i<=lim;i++){
if(不满足条件) continue;
cnt+=dfs(len-1,next state,havelim&&i==lim);
}
return havelim?cnt:dp[len][state]=cnt;//记忆化
}

时间复杂度为O(状态数 * 转移复杂度)


树形动规

用处

当一棵树上不存在会自己改变的变量时,这棵树上通常会存在最优值的传递性。这时对于大部分求最优解的树上问题我们都可以用树形动规来解。

思想

通过儿子转移当前点,或者通过父亲转移当前点,亦或是二者都用到。具体由递归实现。

核心代码:

void dfs(int u,int pre){
for(register int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(v==pre) continue;
......(由u转移到v);
dfs(v,u);
......(由v转移到u);
}
}

时间复杂度为O(状态数 * 转移复杂度)


Tarjan(e-DCC)

用处

求出无向图的边双连通分量,如果分析得出“一个边双之内信息相同”之类的结论,那么可以求出边双连通分量之后缩点,从而把无向图上的问题转化为树上的问题,达到消除后效性的目的。

思想

先任意求出图的搜索树,然后通过时间戳dfn和追溯值low来判断是否构成一个边双。

算法流程

建立一个栈,把遍历到的点加入栈中,并初始化low值等于dfn值。然后考虑当前节点的子节点:

如果没去过,先往下遍历v,那么low[u]=min(low[u],low[v])。

否则low[u]=min(low[u],dfn[v])。

最后如果发现low[u]=dfn[u],那么找到了一个边双,此时不断地将栈顶元素出栈,直到u也出栈为止。这一次出栈的所有点共同构成一个边双。

核心代码

int low[maxn],dfn[maxn],tot;
int stack[maxn],top;
void tarjan(int u){
dfn[u]=low[u]=++tot,stack[++top]=u;
for(register int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int v; cnt++;
do{ v=stack[top--],......(关于边双的操作); }while(v!=u);
}
}
//如果题目不保证图连通,那么在main函数中写这句话:
for(register int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);

时间复杂度为O(N+M)


Tarjan(LCA)

用处

如果题目数据卡log的算法,并且询问存得下,那么就可以存下询问然后用Tarjan离线处理。

优缺点

优点:时间复杂度为O(N+M),不需要预处理,是最快的求LCA的算法。

缺点:不灵活,处理多批LCA会导致代码难度上升。

思想

运用并查集,通过回溯的时候更新的祖先来求LCA。

算法流程

先用邻接表存下询问。建立一个并查集,先dfs遍历到底层,再回溯,并把当前点与回溯后的点合并到一个并查集中去。每到一个点u时遍历询问的邻接表,看是否有与它相连的询问并且询问的另一个v点已经访问过。如果是,那么这个询问的LCA就是v在并查集中的祖先。

int fa[maxn];
bool vis[maxn];
int get(int x){ return x==fa[x]?x:fa[x]=get(fa[x]); }
void tarjan(int u,int pre){
vis[u]=true;
for(register int i=g.head[u];~i;i=g.e[i].next){
v=g.e[i].to;
if(v!=pre) tarjan(v,u),fa[v]=u;
}
for(register int i=q.head[u];~i;i=q.e[i].next){
int v=q.e[i].to;
if(vis[v]) q[i].lca=q[i^1].lca=get(v);
}
} //main函数中
for(register int i=1;i<=n;i++) fa[i]=i;
tarjan(1,1);

时间复杂度为O(N+M)


Tarjan(SCC)

用处

求出有向图的强连通分量,如果题目中说明了“一个强连通分量之内信息相同”之类的句子,那么可以求出强连通分量之后缩点,从而把有向图上的问题转化为DAG上的问题,达到简化问题的目的。

思想

先任意求出图的搜索树,然后通过时间戳dfn和追溯值low来判断是否构成一个强连通分量。

二者的一些概括:

dfn:被遍历到的顺序号。

low:可以从搜索树的子节点追溯到的最小的时间戳。

算法流程

建立一个栈,把遍历到的点加入栈中,并初始化low值等于dfn值。然后考虑当前节点的子节点:

如果子节点不在栈中,先往下遍历v,那么low[u]=min(low[u],low[v])。

否则low[u]=min(low[u],dfn[v])。

最后如果发现low[u]=dfn[u],那么找到了一个强连通分量,此时不断地将栈顶元素出栈,直到u也出栈为止。这一次出栈的所有点共同构成一个强连通分量。

核心代码

int low[maxn],dfn[maxn],tot;
void tarjan(int u){
dfn[u]=low[u]=++tot;
stack[++top]=u,in_stack[u]=true;
for(register int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if(in_stack[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
int v; cnt++;//强连通分量个数+1
do{
v=stack[top--],in_stack[v]=false;
......(关于强连通分量的操作);
}while(v!=u);
}
}
//如果题目不保证图连通,那么在main函数中写这句话:
for(register int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);

时间复杂度为O(N+M)


Tarjan(v-DCC)

用处

求出无向图的点双连通分量,如果分析得出“一个点双之内信息相同”之类的结论,那么可以求出点双连通分量之后缩点,从而把无向图上的问题转化为树上的问题,达到简化问题的目的。

思想

先任意求出图的搜索树,然后通过时间戳dfn和追溯值low来判断是否构成一个点双。

算法流程

建立一个栈,把遍历到的点加入栈中,并初始化low值等于dfn值。然后考虑当前节点的子节点:

如果没去过,先往下遍历v,那么low[u]=min(low[u],low[v])。

否则low[u]=min(low[u],dfn[v])。

遍历子节点v回溯之后如果发现当前节点u为割点,那么找到了一个点双,此时不断地将栈顶元素出栈,直到v也出栈为止。这一次出栈的所有点加上u共同构成一个点双。

核心代码

int low[maxn],dfn[maxn],tot;
int stack[maxn],top;
void tarjan(int u){
dfn[u]=low[u]=++tot,stack[++top]=u;
for(register int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]){
tarjan(v),low[u]=min(low[u],low[v]);
if(dfn[u]<=low[v]){//u为割点
int w; cnt++;
do{ w=stack[top--],......(关于点双的操作); }while(w!=v);
......(把u也处理进点双内);
}
}
else low[u]=min(low[u],dfn[v]);
}
}
//如果题目不保证图连通,那么在main函数中写这句话:
for(register int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);

时间复杂度为O(N+M)


编年史:OI算法总结的更多相关文章

  1. OI算法复习

    搜集一些算法,赛前背一背有好处的 转自各大网站 前排感谢:hzwer.风了咕凉 前辈...Orz 快速读入: int read() { ,f=;char ch=getchar(); ;ch=getch ...

  2. OI算法复习汇总

    各大排序 图论: spfa floyd dijkstra *拉普拉斯矩阵 hash表 拓扑排序 哈夫曼算法 匈牙利算法 分块法 二分法 费马小定理: a^(p-1) ≡1(mod p) 网络流 二分图 ...

  3. DES加解密算法Qt实现

      算法解密qt加密table64bit [声明] (1) 本文源码 大部分源码来自:DES算法代码.在此基础上,利用Qt编程进行了改写,实现了DES加解密算法,并添加了文件加解密功能.在此对署名为b ...

  4. Pollard Rho算法浅谈

    Pollard Rho介绍 Pollard Rho算法是Pollard[1]在1975年[2]发明的一种将大整数因数分解的算法 其中Pollard来源于发明者Pollard的姓,Rho则来自内部伪随机 ...

  5. [zt]摄像机标定(Camera calibration)笔记

    http://www.cnblogs.com/mfryf/archive/2012/03/31/2426324.html 一 作用建立3D到2D的映射关系,一旦标定后,对于一个摄像机内部参数K(光心焦 ...

  6. uoj #31. 【UR #2】猪猪侠再战括号序列 贪心

    #31. [UR #2]猪猪侠再战括号序列 Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://uoj.ac/problem/31 Descript ...

  7. 【Python学习】Python中的数据类型精度问题

    Python真的很神奇...神奇到没有直接的数据类型概念,并且精度可以是任意精度.想当初,第一次接触OI算法时,写得第一个算法就是高精度加法,捣鼓了半天.一切在Python看来,仅仅三行代码即可完成. ...

  8. UOJ#31 【UR #2】猪猪侠再战括号序列

    传送门http://uoj.ac/problem/31 大家好我是来自百度贴吧的_叫我猪猪侠,英文名叫_CallMeGGBond. 我不曾上过大学,但这不影响我对离散数学.复杂性分析等领域的兴趣:尤其 ...

  9. 学长小清新题表之UOJ 31.猪猪侠再战括号序列

    学长小清新题表之UOJ 31.猪猪侠再战括号序列 题目描述 大家好我是来自百度贴吧的_叫我猪猪侠,英文名叫\(\_CallMeGGBond\). 我不曾上过大学,但这不影响我对离散数学.复杂性分析等领 ...

随机推荐

  1. C语言中++*x和*++x的区别

    ++跟*的优先级一样,如果两个同时出现,运算是从右往左(不是常规的从左往右),所以: ++*x即++(*x),先取x的值,然后让值自加1:(地址没变,指针指向的值变了.搞不懂的话自己用快递做例子) * ...

  2. 回顾 2020 年 GitHub 的大事件,你知道多少?

    作者:HelloGitHub-小鱼干 这里是 HelloGitHub 出品的年度盘点系列,本期我们将盘点 GitHub 在 2020 发生的大事件,回顾一下今年 GitHub 给我们带来了那些惊喜.故 ...

  3. C# 多态virtual标记重写 以及EF6 查询性能AsNoTracking

    首先你如果不用baivirtual重写的话,系统默认会为du你加new关键字,他zhi的作用是覆盖,而virtual的关键作用在dao于实现多态 virtual 代表在继承了这个类的子类里面可以使用o ...

  4. [LeetCode]2. Add Two Numbers链表相加

    注意进位的处理和节点为null的处理 public ListNode addTwoNumbers(ListNode l1, ListNode l2) { int flag = 0; ListNode ...

  5. SpringBoot 获取微信小程序openid

    最近做一个项目用到小程序,为了简化用户啊登录,通过获取小程序用户的openid来唯一标示用户. 1.官方教程 2.具体步骤 3.具体实现 4.可能出现的错误 5.代码下载 1.官方教程 先来看看官方怎 ...

  6. myeclipse经常弹出Subversion Native Library Not Available框解决办法

    两种解决方案:(1)在myeclipse中选择 "Windows" -> Perferences. 然后通过左上方的筛选,选出svn设置菜单,点解左侧的"SVN&q ...

  7. Springboot 添加druid监控

    pom <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifa ...

  8. SpringBoot进阶教程(六十九)ApplicationContextAware

    在某些特殊的情况下,Bean需要实现某个功能,但该功能必须借助于Spring容器才能实现,此时就必须让该Bean先获取Spring容器,然后借助于Spring容器实现该功能.为了让Bean获取它所在的 ...

  9. Spring Cloud是什么鬼?

    研究了一段时间Spring Boot了准备向Spring Cloud进发,公司架构和项目也全面拥抱了Spring Cloud.在使用了一段时间后发现Spring Cloud从技术架构上降低了对大型系统 ...

  10. druid监控

    1 @ConfigurationProperties(prefix = "spring.datasource") 2 @Bean 3 public DataSource druid ...