算法入门

每个专题结束后会有5小时的专题赛~

1、hustOJ目前支持谷歌、火狐浏览器等部分浏览器。

2、欢迎吐槽~

3、推荐该阶段用书(以下具体算法实现多数可在此书中找到详解):算法竞赛入门经典之训练指南(刘汝佳)

4、题解报告:专题中的题目多是经典题目,百度搜索即有详细解答~

5、专题相关知识点红字标出,建议先百度红字部分,有助于专题学习~

6、专题时间会在"ACM 今天你AC了吗?"(126270450)群中消息提醒~

7、过去的恋情和专题不要再留恋啦~,time waits no man !

8、对于图论入门:可以先记忆屈婉婷的《离散数学》图论部分(页数不是很多)了解图论,然后转跳第三步

9、0基础新手起步推荐教程:点击打开链接(C语言教程+点击打开链接题目练习)(若无法解出题目可以百度 hdu + 题号,看网上的解析)大约做50道以上就可以进行下面的步骤

=================================

比赛注意点(don't ask why, just do it.):

1、浮点数输出若用c, 则用%f

2、STL的.size()等返回的是unsigned int, 与int比较时转成(int)G.size()

3、java的BigInteger数组不能乱开, 尽可能省

4、比起线段树, 优先考虑树状数组

-------------------------------------------------------

学习栈和队列

学习递归版GCD的使用

-------------------------------------------------------

BFS+DFS 搜索

专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=39334#overview  (前5题难度稍有增强,N题是八数码推荐跳过)

(注意知识点:二进制状态压缩,队列形参,debug,时间复杂度)

专题资料推荐:刷几个题就OK啦~(白书的入门经典)

专题赛:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=39493#overview

专题概述:大家还是很热情的嘛~

-------------------------------------------------------

 

并查集、最小生成树、递推、同余

(注意知识点:树的概念图的概念非常重要,请自行百度))

关于图:图由点和边组成,对于n个点m条边的图(此专题下边都是无向的)我们把点标号由1-n(或0-n-1),边则通过相连的两个端点[u,v] 来区分。

关于树:对于n个点的图,我们最少需要n-1条边相连,当且仅当n个点用n-1条边相连时称为树。

显然对于树上的任意两点u、v,都有一条唯一确定的路径。

有根的称为有根树,无根的称为无根树

我们在绘制树时让根在最高处,依次向下延伸。


简述一下并查集:

并查集有2件东西:点和点所属的集合。 我们用Father[i] 表示 i 点所属的集合 为 Father[i],(即为了区别所有的集合,我们给集合标号从1-n,在最开始时 i点属于i集合)

我们初始化每个点都不在一个集合(即相互独立) --- Father[i] = i

我们用函数(查找):

int find(int x){return x == Father[x] ? x : Father[x] = find(Father[x]) ; } 

来寻找 x 的最终父亲 FA, 并把路径上所有的点都归入 FA

合并:

void Union(int x,int y){
	int fx = find(x), fy = find(y);
	if(fx == fy)return ;
	Father[fx] = fy;
}

对于合并过程可以用路径压缩进行优化:正确姿势是用一个rank数组(就是集合的秩"即集合元素个数")把个数少的集合归并到集合多的集合下。

 

简述一下最小生成树:

最小生成树可以先学习kruskal (需用到并查集)

简述一下kruskal:我们先对边按权值小到大排序,然后从小到大对边判断是否选择(当且仅当边 E 所连接的端点[u,v] 不在同一集合中就说明[u,v]不相连,我们才选 E,并把 [u,v] 所在的集合归并,显然当所有点都在一个集合中,说明他们都已经被连接,则最小生成树完成)

最小生成树的应用:

给定n个点m条无向带权边的图,使得(任意两点之间 最大边权)最小,问这个最小的权值是多少。

这种 (两点之间 最大边权)最小的路径我们称作瓶颈路,可以先求个最小生成树,这样任意一条路径的最大边权一定是最小的。

专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=39621#overview

 专题赛:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=39794#overview

 专题概述:专题赛里的几个题还是很给力的~

-------------------------------------------------------

 

背包、矩阵快速幂、素数、二分查找

专题资料推荐:背包九讲,素数表的三种打发(给出一种,另一种常有的是prime[i] = true表示i为素数),矩阵快速幂模版。

1、注意对于素数的处理,预先打出素数表这种方式我们称预处理,可以避免回答问题时对相同的结果进行重复计算。

2、若快速幂尚不理解,可转百度百科。

3、这里拓展一个分段法,可以在数据量很大但询问数很少的一类题中使用(非正规解法)相关题目及题解

专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=39850#overview

专题赛:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=39949#overview

 专题概述:背包是一类基础的动态规划,注意状态转移时只能从已知到未知。


-------------------------------------------------------


优先队列(最小堆)、状态压缩、单源最短路

最短路建议先学习spfa算法. 重要专题请耐心AK, 若有任何疑问可以在群里提出!

简单给出一个spfa的形式:

int dis[N];//N个点 此处给出邻接表写法(若不熟悉可以下拉查看邻接表的示意图)
int spfa(int start, int end, int n){//最短路的起点,终点,图的下标[1,n]
	for(int i = 1; i <= n; i++)dis[i] = 100000000;
	dis[start] = 0;
	queueq; q.push(start); 
	while(!q.empty()){
		int u = q.front(); q.pop();
		for(int i = head[u]; i!=-1; i = edge[i].next){
			int v = edge[i].to;        //遍历 以u为起点的边 的终点
			if(dis[v] > dis[u]+edge[i].dis)	{
				dis[v] = dis[u]+edge[i].dis;
				q.push(v);
			}
		}
	}
	return dis[end];
}

1、注意对于最短路中存在负环判定:对于spfa算法,当某个点入队列(入队列的意义就是该点被松弛了(更新))次数>n次,就说明该点在负环上(可以简单证明一个点至多被更新n次(n为图中的顶点数))。

2、优先队列:类似于堆,出队的元素不是在队尾的元素,而是队列中最小的元素(我们有时可以在队列中存储结构体元素,只需重载运算符即可)。

示例:

struct node{
	int x, y;
	bool operator<(const node&a) const
	{ if(a.x==x) return a.y

3、状态压缩:当某些状态只有true or false,时我们可以用一个整数来表示这个状态。

示例:

有3块不同的蛋糕编号1、2、3, 被老鼠啃过, 那么蛋糕只有2种状态, 我们用0表示没有被啃过, 1表示被啃过。

显然我们可以得到所有状态:000、001、010、011、100、101、110、111.

而上述二进制数对应的整数为 [0, 2^3) . (如二进制011 = 整数3表示 第2、3块蛋糕被啃过,第一块蛋糕没有被啃过)

我们可以用 for(int i = 0; i < (1<<3); i++) 来遍历所有的状态。

把多个事物的状态利用二进制含义压缩为一个整数称为状态压缩。

4、利用优先队列优化最短路时, 我们可以先出队距离起点最近的点, 则若出队的为终点显然我们已经得到了一条最短路了。

专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=40014#overview

专题赛:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=40141#overview

 专题概述:过年了,祝大家新年快乐~


-------------------------------------------------------

 

树的遍历、简单博弈、欧拉路径、Floyd算法

1、树的遍历分先序、中序、后序,由dfs完成(主要区别在于遍历左右子树的优先顺序和输出语句位置)

2、Floyd求传递闭包。给定邻接矩阵通过对每个点的松弛求出任意点间的距离(复杂度为O(n^3))。

3、取石子类的简单博弈可以参看此处。

简单描述一下Floyd:首先我们需要一个邻接矩阵(所谓邻接矩阵是一个 n*n 的矩阵, 第i行第j列的值为value 表示i点到j点的距离为value.若i到j点不可达时我们可以使value=inf)

注意传递闭包的概念, 得到一个传递闭包至多将任意两点松弛n次。第一层for是用k点去松弛, 第二层和第三层for是对于任意两点i、j。

Floyd代码:

#define inf 1000000000
// init***************
for(int i = 1; i <= n; i++)
	for(int j = 1; j <= n; j++)
		dp[i][j] = inf;
//****************
//--------------Floyd: 
for(int k = 1; k <= n; k++)
	for(int i = 1; i <= n; i++)if(i!=k && dp[i][k] != inf)
		for(int j = 1; j <= n; j++)if(j!=i && j!=k)
			dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
//--------------
for(int i = 1; i <= n; i++) dp[i][i] = 0;

 

专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=40505#overview

 

-------------------------------------------------------

(注意知识点:STL的vector容器,常用方法不多请自行百度)

建议自学掌握基础知识:对于图的储存(邻接表、邻接矩阵)

简述下邻接表:

struct Edge{
	int to, next;
}edge[MAXN];//MAXN为边数
int head[N], edgenum;//N为点数
void addedge(int u, int v){
	Edge E={v,head[u]};
	edge[edgenum] = E;
	head[u] = edgenum++;
}
void init(){ memset(head, -1, sizeof(head)); edgenum = 0; }//注意表头的初始化

算法入门_第1张图片

遍历点u所连接的所有边:

    for(int i = head[u]; i != -1; i = edge[i].next){
    	edge[i]; //edge[i]就是x点所连接的所有的边
    }


网络流、网络流求最小割、最小割定理

1、简述一下最小割:对于一个图,我们要删除一些边使得 1点与n点不连通。

删边的费用为边权值,则总边权和就是 一个可行解的割边集的权值

当费用最小时,我们称为最小割。

 

最小割 = 最大流 做一个简要证明:

我们要找一个 1点和n点的最小割 边权和(这个答案是 1点到n点的最大流)

首先我们把1、n作为源点和汇点,跑一次网络流

那么对于某条流, 显然说明了这条流是 连接着1点和n点的一条路径。

这条路径我们必须去掉,当然删除这条路径上的任意一条边就可以认为去掉了这条路径。

而这条路径上的任意边 边内的流量就是 这条路径的流量

因此为了得到删除这条路径的最小费用,我们选择这条路径上满流的边(这样不会有多余的费用产生)

此时删边的费用=边权值=流量

 

对于每条连接着1-n的路径都这样操作, 就能得到:最大流 = 最小割

 

(注意以上1点和n点只是举例,可以替换为任意两点或者任意两个点集,而非具体的定义)

补充:对于上述所说的某条路径:路径上的边必然有一条或者多条是满流的。

我们可以用反证法:假设所有边都是不满流的,此时我们还可以再在这条边上增加流量直到某条边满流为止。

 

2、网络流的建图是重点。

1)可以通过虚拟一个源点(汇点)来限制流入(流出)整个网络的流量

比如:当源点为1时,我们用 0 作为源点 并建一条 0->1 边权为C的边,这样就能限制流入流量为C。

2)当有很多个源点时,我们也可以建一个虚拟源点来连接所有源点,这样就只有1个源点了。

3)对于某个点 i ,我们可能只允许流过C流量,则此时我们把i点拆开(就是用两个点来表示i点(比如 i 和 i+N )) 然后 i 与 i+N 中间建一条边权为C的边 来对i点限流。

3、可先学习白书的递归版dinic,然后手敲过题。

白书模版请点我。←边板子的水题请点我。

各类网络流模板:http://www.notonlysuccess.com/index.php/algorithm-of-network/

专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=41551#overview

专题概述:熟悉网络流算法后可以百度下载pdf:[网络流建模汇总][Edelweiss]了解各式网络流建图方法。

-------------------------------------------------------


完全二叉树、线段树、线段树的Lazy操作

线段树资料:http://blog.csdn.net/metalseed/article/details/8039326

线段树的应用-04国家队论文

胡浩线段树题集及代码模式:http://www.notonlysuccess.com/index.php/segment-tree-complete/

一个木有模板的专题,请多仔细阅读资料(然后刷题)

线段树学习:

0、04年国家队论文、白书

1、建议学习胡浩版的线段树(即一个节点用一个结构体来表示

struct node{
    int l, r;
    int val;
}tree[N*4];
这样容易理解线段树的结构(如果不熟悉线段树的结构,可以在vs2012以上版本的编译器的单步调试中查看tree这个变量,会比较清楚地看到线段树的酷炫结构,这是帮助理解的 重要一步

本博客的线段树也是hh牛那里学习的,较容易形成模版化减少出错。

2、线段树的另一个重要功能:延迟操作

比如我们对一个数组a有2种操作

  一、区间[l,r] 每个数+ val

 二、单点求值

struct node{
    int l, r;
    int sum, lazy;
}tree[N*4];

那么其实如果我们修改了1000次[1,n]区间,而在第1001次才求某点的值,那么我们不用急着把每个点更新了

而是在[1,n]这个区间做一个标记,表示这整个区间的数都被加上了一个值,那么前1000次操作都只需要对lazy修改即可。

等询问时再把这个lazy标记传到下面的区间去。

这种操作就叫延迟操作
3、线段树的延迟操作,建议写成当前区间最新,即当这个区间有lazy的标记时,这个区间也要保持最新


 专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=41967#overview


-------------------------------------------------------


2-SAT、简单博弈

(注意知识点:对STL的set集合学习)

set用法简介:wenku.baidu.com/view/b71a8b524431b90d6c85c746.html

2-sat的简要在↓前面,一般可以用dfs或者tarjan缩点判断,这里暂且推荐dfs版本,较容易理解且编程复杂度不会太高。

2-sat 白书模版

专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=42657#overview


-------------------------------------------------------


字典树、KMP

字典树:一个重要特色就是省内存, 多个前缀相同的字符串只需要记录一次(言外之意:对于某节点的所有子树,他们的字符串都是有公共前缀的)

字典树资料:百度百科

字典树模版:blog.csdn.net/acmmmm/article/details/12250267

字典树的代码建议先学习白书的版本。

简述一下字典树:

对于以下的5个字符串,我们用普通的方法需要16个字节储存。

********************************************************

1、abcd

2、abd

3、bcd

4、efg

5、hij

********************************************************

我们观察字符串 1、2,发现他们有公共的前缀 "ab", 所以我们可以用一个"ab"来表示所有公共前缀为"ab"的字符串。(这样我们只需要用14个字节储存

想象一下,那么对于某一个节点u来说,u的所有子节点都是有相同的公共前缀。

对于u这个节点,我们需要记录的是u的26个子节点(因为有26个字母嘛)

对于每个节点,为了区别他们,用一个整数表示一个节点。

数组:int ch[u][26]; 表示u节点 的26个指针。例如:我们要查找 (u 接下来的字母为a)则 用 ch[u][ 0 ] 表示即可。(而接下来的字母为 z 则用ch[u][25]表示。)

注意:因为每个字符串的第一个字母都不一定相同,所以我们虚拟一个根节点来连接所有字符串的第一个字母(下图中空白点就是虚拟节点)

(注:字母是表示箭头下方的节点)

对于某个节点,我们都能直接确定一个序列(则对于一个整数(上面提到一个节点用一个整数来表示)就能直接确定一个序列)


算法入门_第2张图片

此时我们用 int Have_word[u]; 表示 u 这个(整数)序列的单词有多少个。

则每次插入单词时,在单词最后一个字母所在的整数 v ,Have_word[v] ++;

(如上例子在每个节点记录信息来实现字典树上的各种功能)

注意:对于任意一个节点,都是确定的一个字符串序列。


KMP:推荐先围观白书的211页

KMP个人简介(纯粹广告):blog.csdn.net/acmmmm/article/details/9863495

KMP其他资料:blog.csdn.net/yaochunnian/article/details/7059486

KMP的复杂度是线性的,即O(n+m);

关于KMP的2个版本:普通KMP的失配数组 next[0] = 0, 滑步函数优化的失配数组 next[0] = -1;

这里推荐先学习普通版本KMP,滑步函数据说速度稍快,但失去了KMP本身的含义,且普通KMP的速度对于比赛已经足够快了。


专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=43273#overview


-------------------------------------------------------


有向图的强连通分量、缩点

 强连通是对有向图求环的算法,tarjan(相当于dfs)

主要是环具有些特性,因此把环视为一个点,对图中环进行缩点,并给图重新标号

模版性较强。

强连通算法可参考白书319页 或 这里

更高端的在这里:www.byvoid.com/blog/scc-tarjan/

强连通模版

专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=44430#overview

-------------------------------------------------------

 
Current:
 

RMQ问题、LCA转RMQ、树状数组

RMQ问题:区间求最值,可以用线段树等解决; 详见白书197页

LCA:最近公共祖先,可以用离线的tarjan,在线的LCA转RMQ(预处理O(nlogn),询问O(1),LCA倍增法(预处理O(nlogn),询问O(logn))

模版变动不大,主要多做题。


树状数组:对于一个数组,可以区间求和,支持单点更新,复杂度均为O(logn); 详见白书194页

简述一下树状数组:

int c[N], maxn; //maxn的值为区间的最大值

树状数组中调用的lowbit(x) = x的二进制下后面0的个数 +1 lowbit(1)=1; lowbit(2)=2;lowbit(3)=1; 

第二个函数 void change(int pos, int val){} //给数组下标为pos的 a[pos]+=val;

第三个函数 int sum(int pos){} //[1,pos]区间的和 等价于 int ans = 0; for(int i = 1; i <= pos; i++) ans += a[i]; return ans;

注意初始化 void init(){memset(c, 0, sizeof c); maxn = n;}

树状数组的代码量较小,且有点模版化,模版改动不大,可以先试着套模版多过题再了解。

注意:若要求区间[0,pos]的和,0 这个点特殊处理一下就好。

拓展:树状数组的区间操作

LCA题集 

LCA倍增法模版

LCA转RMQ解法示意

专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=44921#overview

-------------------------------------------------------



·数位dp、单调队列、滚动数组、费用流


简述一下费用流:

对于一个网络流,其实我们满流有多种不同的方法,如下图我们有两种方法使得满流。

算法入门_第3张图片

有时流过一些边需要一定的费用,则我们希望在满流的情况下费用最少,就是最小费用最大流。

我们只需把白书上的dinic的BFS找一条可行流的代码改成spfa(最短路,源汇点为起末点,费用为边权,找一条费用最小的(就是最短路)进行增广)

 边的总费用=边费用*边流量。

-------------------------------------------------------

·差分约束


算导内容大致简介:利用最短路求解。

给定n个点m条约束,每个点都有一个点权。

约束条件形如:value(u) - value(v) <= x, 则v点向u点连一条有向边,边权为x。

再用一个超级源点和每个点连一条边权为0的有向边。然后以超级源点为起点跑一次最短路就能得到一个解。

若图中存在负环则此差分约束系统无解。

若有解,则最短路中的dis数组就是一个可行解。

-----

拓展(引用自点击打开链接)

差分约束系统的最大解与最小解
算法导论只提到了约束图G(V,E):w(u,v)能对应形如x[v]-x[u] <= w(u,v)的约束系统。
事实上,G(V,E):w(u,v)还能对应形如x[v]-x[u] >= w(u,v)的约束系统,只不过此时约束系统有解对应每个点的最长路径长度D[v]都存在(图中无正权环)
。证明过程类似于前一种约束系统,使用到最长路的三角不等式,参见算法导论p371。


差分约束系统的最大解是针对前一种约束系统而言的,指在某个变量确定的情况下,其他所有变量都取到所能取的最大值。
最小解则针对后一种约束系统,其定义可以类比。


我们将看到,在某个变量确定的情况下,约束图以超级源点(假设编号为0)为根的最短路径树所给出的解是前一种约束系统的最大解,同理,其最长路径树是后一种约束系统的最小解。


我们用前面一条来说明,后面一条的可以类比得到。


为何这样求得的是最大解证明:

假设约束系统存在解,我们知道,给定超级源点的一个偏移量d[0](即d[0]不一定为0),就能由最短路径树确定差分约束系统的一组解(树上两点间路径唯一确定)。
同样的,给定最短路径树上任一个节点的值d[v],都可以求出超级源点的偏移量d[0],同样也确定了差分约束系统的一组解。
对于从0到v的任意一条路径p(0,v1,v2,...,vN,v),其所表示的约束式分别为:x[v1]-x[0] <= 0;x[v2]-x[v1] <= w(v1,v2);...;
x[v]-x[vN] <= w(vN,v)。叠加得到x[v]-x[0] <= w(p),令x[0]为确定值d[0],即x[v] <= d[0]+w(p),p为从0到v的任意路径。
取p为0到v的最短路p*,就有x[v] <= x[0]+w(p*) <= x[0]+w(p)。
则当x[v]=d[0]+w(p*)时x[v]取得最大值。

例题

------------------------------------------------------

乘法逆元:

(a / b)%mod  =  a * (b^(mod-2))

b^(mod-2)套个快速幂,复杂度是log(mod), 基本是一个常数。



-------------------------------------------------------

·无向图的割顶和桥、树的重心

 

-------------------------------------------------------

 

·无向图的双连通分量


-------------------------------------------------------

 

·拓展欧几里德、AC自动机

 

-------------------------------------------------------

 

·二分匹配、基数排序

二分匹配的图论相关:(1)(2)

 二分匹配的定义:(1)

-------------------------------------------------------

 

·后缀数组

 

-------------------------------------------------------

 

 ·次小生成树、区间dp

 

-------------------------------------------------------

 当current走到这里时,再往前看,看以前的题和以前的自己,一定会惊讶自己走了这么远~

你可能感兴趣的:(ACM套题)