zoukankan      html  css  js  c++  java
  • 算法入门

    http://blog.csdn.net/qq574857122/article/details/18039855

    每个专题结束后会有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

    我们用函数(查找):

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


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

    合并:

    1. void Union(int x,int y){  
    2.     int fx = find(x), fy = find(y);  
    3.     if(fx == fy)return ;  
    4.     Father[fx] = fy;  
    5. }  

    对于合并过程可以用路径压缩进行优化:正确姿势是用一个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的形式:

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

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

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

    示例:

    1. struct node{  
    2.     int x, y;  
    3.     bool operator<(const node&a) const  
    4.     { if(a.x==x) return a.y<y;  return a.x<x; } //根据x,y值比较node结构体的大小  
    5. };  

    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代码:

    1. #define inf 1000000000  
    2. // init***************  
    3. for(int i = 1; i <= n; i++)  
    4.     for(int j = 1; j <= n; j++)  
    5.         dp[i][j] = inf;  
    6. //****************  
    7. //--------------Floyd:   
    8. for(int k = 1; k <= n; k++)  
    9.     for(int i = 1; i <= n; i++)if(i!=k && dp[i][k] != inf)  
    10.         for(int j = 1; j <= n; j++)if(j!=i && j!=k)  
    11.             dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);  
    12. //--------------  
    13. 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容器,常用方法不多请自行百度)

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

    简述下邻接表:

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

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

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



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

    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、建议学习胡浩版的线段树(即一个节点用一个结构体来表示

    1. struct node{  
    2.     int l, r;  
    3.     int val;  
    4. }tree[N*4];  

    这样容易理解线段树的结构(如果不熟悉线段树的结构,可以在vs2012以上版本的编译器的单步调试中查看tree这个变量,会比较清楚地看到线段树的酷炫结构,这是帮助理解的重要一步)

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

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

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

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

     二、单点求值

    1. struct node{  
    2.     int l, r;  
    3.     int sum, lazy;  
    4. }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]表示。)

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

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

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



    此时我们用 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、单调队列、滚动数组、费用流


    简述一下费用流:

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

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

    我们只需把白书上的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走到这里时,再往前看,看以前的题和以前的自己,一定会惊讶自己走了这么远~

  • 相关阅读:
    I/O模型
    同步异步与协程
    GIL(全局解释器锁)
    解决pycharm启动慢
    操作系统发展史
    TCP和UDP
    粘包问题
    网络编程
    异常
    常用函数汇总
  • 原文地址:https://www.cnblogs.com/handsomecui/p/4967264.html
Copyright © 2011-2022 走看看