写于一只蹲在角落的蒟蒻-Z__X...
2020.2.7,图论和 \(dp\) 终于告一段落。蓦然回首,好似已走过许多...不曾细细品味,太多太多又绵延不断地向我涌来...
谨以此纪念 逝去 的图论和 \(dp\);

图论

  • 图的存储

    首先,图论的基础:存储。这里介绍几种存储结构;

    邻接矩阵

    一种最简单,暴力的存储结构,二维数组存储;

    注:这是读入方式的一种,具体看题目。

     	cin >> n >> m;
    for (int i=1;i=m;i++)
    {
    cin >> i >> j >> x;
    a[i][j]=a[j][i]=x;
    }

    邻接表(链式前向星)

    邻接表,又叫链式前向星,其实就是链表的思路;

    先开一个 \(linkk\) 数组,\(linkk[i]\) 表示的是以 \(i\) 为起点第一条边的编号,\(e\) 数组存边,\(e[i].y\) 表示终点,\(e[i].v\) 表示权值,\(e[i].next\) 表示下条边的编号;

    邻接表核心就是一个插入函数:

    void insert(int x,int y,int v)  //x为起点,y为终点,v为权值。
    {
    e[++t].y=y; e[t].v=v;
    e[t].next=linkk[x]; linkk[x]=t;
    }

    还有一个循环同样重要,类似于查询:

    for (int i=linkk[x];i;i=e[i].next)

    边表(边集数组)

    一种简便的存储结构,思路同样很简单,就是把所有的边存储到 \(e\) 数组中,要存储起点,终点,权值。

    struct  node
    {
    int x,y; //起点和终点
    int v; //权值
    }e[maxm];
  • 图的遍历

    dfs遍历

    邻接矩阵 \(dfs\) 遍历:

    void dfs(int k);
    {
    printf("%d",k);
    f[k]=true;
    for (int i=1;i<=n;i++)
    if (!f[i] && a[k][i]) dfs(i);
    }

    邻接表 \(dfs\) 遍历:

    void dfs(int k)
    {
    for (int i=linkk[k];i;i=e[i].next)
    if(!vis[e[i].y])
    {
    vis[e[i].y]=1;
    dfs(e[i].y);
    }
    }

    bfs遍历

    邻接矩阵 \(bfs\) 遍历:

    void  bfs(int i);
    {
    memset(q,0,sizeof(q));
    int head=1,tail=1;
    q[1]=i; f[i]=true;
    while (head<=tail)
    {
    k=q[head]; cout>>k;
    for (int j=1;j<=n,j++)
    if (a[k][j] && !f[j])
    {
    q[++tail]=j;
    f[j]=true;
    }
    head++;
    }
    }
  • 最短路

    Floyd

    以 \(k\) 为中转点,更新 \(i\) 到 \(j\) 的最短路

    for (int k=1;k<=n;k++)
    for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++)
    if (dis[i][k]+dis[k][j]<dis[i][j])
    dis[i][j]=dis[i][k]+dis[k][j];

    Dijkstra

    每次在未求点中找一个离起点距离最短的点,标记,并用这个点作为中转点更新未标记的点的最短路

    注意:\(Dijkstra\) 不支持负权

    void dijkstra(int st);
    {
    memset(f,0,sizeof(f));
    memset(dis,0,sizeof(dis));
    for (int i=1;i<=n;i++) dis[i]=a[st][i];
    f[st]=1; dis[st]=0;
    for (int i=1;i<n;i++)
    {
    int min=0xfffffff, k=0;
    for (int j=1;j<=n;j++)
    if (!f[j] && (dis[j]<min)) min=dis[j],k=j;
    if (k==0) return; //找不到距离最短的点了
    f[k]=1; //把k加入集合1;
    for (int j=1;j<=n;j++) //三角形迭代更新最短距离
    if (!f[j] && dis[k]+a[k][j]<dis[j]) dis[j]=dis[k]+a[k][j];
    }
    }

    Bellman-ford

    支持负环

    int bellman_ford()
    {
    memset(dis,10,sizeof(dis)); //初值
    dis[1]=0; //起点到起点的距离为0
    for (int i=1;i<=n;i++) //最多迭代n次
    {
    bool p=0; //是否有松弛标记
    for (int j=1;j<=tot;j++) //tot条边
    {
    int ax=a[j].x,ay=a[j].y,av=a[j].v; //第j条边起点,终点,长度
    if (dis[ax]+av<dis[ay]) //三角形迭代
    {
    dis[ay]=dis[ax]+av;
    p=1; //松弛标记
    }
    }
    if (p==0) return 0; //无松弛
    }
    return 1; //有负环
    }

    SPFA

    \(Bellman-ford\) 的升级版,对 \(Bellman-ford\) 的迭代进行了改进,每次只要从上次刚被“松驰”过的点 \(x\),来看看 $x¥ 能不能松驰其它点即可,用BFS中的队列来存放刚被“松驰”过的点;

    void SPFA(int k)  //SPFA模板
    {
    memset(dis,10,sizeof(dis)); //初值
    memset(vis,0,sizeof(vis)); //清空标记数组,vis[k]表示点k是否在队列中
    dis[k]=0;
    vis[k]=1;
    int head=1,tail=1;
    q[head]=k; //点k进队
    for (head=1;head<=tail;head++)
    {
    int x=q[head]; //取出队头
    for (int i=linkk[x];i>0;i=e[i].next) //邻接表查询
    {
    if (dis[x]+e[i].v<dis[e[i].y]) //迭代
    {
    dis[e[i].y]=dis[x]+e[i].v;
    if (vis[e[i].y]==0) //若此点没有标记,标记,进队
    {
    vis[e[i].y]=1;
    q[++tail]=e[i].y;
    }
    }
    }
    vis[x]=0; //出队
    }
    }
  • 最小生成树

    Prim

    注意:这边给出的代码求的是最小生成树的边权和

    void prim(int st)  //prim算法
    {
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    for (int i=1;i<=n;i++) dis[i]=a[st][i]; //赋值交叉边
    vis[st]=1; int ans=-1e9;
    for (int i=1;i<=n-1;i++)
    {
    int Min=1e9,mini=0;
    for (int j=1;j<=n;j++)
    if (!vis[j] && dis[j]<Min) Min=dis[j],mini=j; //找到距离最近的点
    vis[mini]=1; //标记
    ans+=dis[mini]; //累加边权和
    for (int j=1;j<=n;j++)
    if (!vis[j] && dis[j]>a[mini][j])
    {
    dis[j]=a[mini][j]; //迭代
    }
    }
    printf("%d",ans);
    }

    Kruskal

    按边权从小到大排序,然后从小到大依次取边,需要用到并查集的算法判断加入边 \((x,y)\) 是否形成环,如果有环,就放弃此边,否则加入边 \((x,y)\)。如果已经加入了 \(n-1\) 条边,结束;

    \(Kruskal\) 算法最难点在于怎样判断加入边 \((x,y)\) 后是否形成了环,判断边 \((x,y)\) 的两个顶点 x,y 在图中是否已经连通,在并查集中只要直接判断他们的父亲是否相同就可以了。

    void Kruskal(int k)  //克鲁斯卡尔
    {
    for (int i=1;i<=n;i++) father[i]=i; //开始每个点的父节点都是它本身
    int cnt=0;
    for (int i=1;i<=m;i++)
    {
    int v=getfather(e[i].x);
    int u=getfather(e[i].y);
    if (v!=u) //判断e[i].x和e[i].y是否存在于同一集合内
    {
    merge(u,v); //合并
    ans+=e[i].v;
    if (++cnt==n-1) //最小生成树生成结束
    {
    break;
    }
    }
    }
    }
  • 拓扑排序

    对有向无环图的点进行先后排序;

    这里用队列来维护拓扑序

    void Topsort()  //拓扑排序
    {
    int head=1,tail=0;
    for (int i=1;i<=n;i++)
    if (in[i]==0) q[++tail]=i; //初始化
    for (head=1;head<=tail;head++)
    {
    int x=q[head]; //取出队头
    for (int i=linkk[x];i;i=e[i].next) //邻接表查询
    {
    int y=e[i].y;
    if (--in[y]==0) q[++tail]=y; //入队
    }
    if (tail==n) return;
    }
    }
    注意:
    1. 有向图的拓扑序列不唯一;
    2. 如果图中存在环,无拓扑序列;
    3. 还有一种写法用栈来实现拓扑排序。

dp

  • dp入门

    背包

    具体戳这儿

    递推

    这里给一个例子,不再多说

    给定一个由 \(n\) 行数字组成的数字三角形,如下图所示:

       7

       3 8

       8 1 0

       2 7 4 4

       4 5 2 6 5

    试设计一个算法,计算出从三角形的顶至底的一条路径,使该路径经过的数字总和最大(每一步只能从一个数走到下一层上和它最近的左边的数或者右边的数)。

    	for (int i=1;i<=n;i++)
    for (int j=1;j<=i;j++)
    {
    cin >> a[i][j];
    }
    for (int i=1;i<=n;i++)
    for (int j=1;j<=i;j++)
    {
    f[i][j]=max(f[i-1][j],f[i-1][j-1])+a[i][j];
    }

    LCS-最长不上升子序列问题、LIS-最长不下降子序列问题

    有一道经典的题目囊括了这两个问题:导弹拦截。

    为了拦截敌国的袭击,科学家研发出一套导弹系统,导弹系统有个缺陷:第一发炮弹可以到达任意高度,然而之后的每一发炮弹都不能高于前一发的高度。

    现给出数个导弹的高度 \(h[i]\) ( \(h[i]\)为\(<=50000\)的正整数 ),计算一套导弹拦截系统最多可以拦截多少导弹,如果需要拦截全部导弹需要多少套导弹拦截系统?

    思路:

    第一问很显然,就是一个 \(LCS\) 的模板;

    第二问是一个 \(LIS\) 的模板,为什么呢?(蒟蒻不会 呜呜呜~)

    	for (int i=1;i<=n;i++) //LCS
    {
    dp[i]=1;
    for (int j=0;j<=i-1;j++)
    if (h[i]<=h[j]) dp[i]=max(dp[i],dp[j]+1);
    }
    int Max=-0xfffffff;
    for (int i=1;i<=n;i++) Max=max(Max,dp[i]);
    cout << Max << endl;
    for (int i=1;i<=n;i++) //LIS
    {
    dp[i]=1;
    for (int j=0;j<=i-1;j++)
    if (h[i]>h[j]) dp[i]=max(dp[i],dp[j]+1);
    }
    Max=-0xfffffff;
    for (int i=1;i<=n;i++) Max=max(Max,dp[i]);
    cout << Max << endl;
  • 区间dp

    好像就是一个板子套用一下吧,这里就列一下最经典的石子合并问题。

    在一个圆形操场的四周摆放 \(N\) 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的 \(2\) 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

    试设计出一个算法,计算出将 \(N\) 堆石子合并成 \(1\) 堆的最小得分和最大得分。

    	for (int i=1;i<=n;i++)
    {
    cin >> a[i];
    sum[i]=sum[i-1]+a[i]; //前缀和,前i个石子堆的总数量
    }
    memset(f,10,sizeof(f));
    for (int i=1;i<=n;i++) f[i][i]=0;
    for (int len=2;len<=n;len++) //区间长度
    {
    for (int i=1;i<=n-len+1;i++) //左端点
    {
    int j=i+len-1; //右端点
    for (int k=i;k<=j;k++)
    f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]); //i到j的区间最优值通过中间点k来更新
    }
    }
    cout << f[1][n];
  • 二维dp

    二维 \(dp\),最经典的应该还是马拦过河卒了吧,这边拿这道题做例子;

    棋盘上 \(A\) 点有一个过河卒,需要走到目标 \(B\) 点。卒行走的规则:可以向下、或者向右。

    同时在棋盘上 \(C\) 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。

    棋盘用坐标表示,\(A\) 点 \((0, 0)\)、\(B\) 点 \((n, m)(n, m\)为不超过 \(15\) 的整数\()\),同样马的位置坐标是需要给出的。现在要求你计算出卒从 \(A\) 点能够到达 \(B\) 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

    	scanf("%d %d %d %d",&n,&m,&x,&y);
    n++;m++;x++;y++;
    memset(f,0,sizeof(f));
    for (int i=0;i<=8;i++)
    {
    int xx=x+fx[i],yy=y+fy[i];
    if (xx>=1 && xx<=n && yy>=1 && yy<=m)
    {
    f[xx][yy]=1;
    }
    }
    dp[1][1]=1;
    for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
    if (f[i][j]==0 && !(i==1 && j==1))
    {
    dp[i][j]=dp[i-1][j]+dp[i][j-1];
    }
    printf("%d",dp[n][m]);
  • 双进程dp

    emm...以最长公共子序列为例吧。

    字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。

    令给定的字符序列\(X=“x0,x1,…,xm-1”\),序列\(Y=“y0,y1,…,yk-1”\)是\(X\)的子序列,存在X的一个严格递增下标序列,使得对所有的\(j=0,1,…,k-1,有xij = yj\)。

    例如,\(X=“ABCBDAB”\),\(Y=“BCDB”\) 是\(X\)的一个子序列。

    对给定的两个字符序列,求出他们最长的公共子序列。

	while (cin >> ch,ch!='.') a[++n]=ch;
while (cin >> ch,ch!='.') b[++m]=ch;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if (a[i]==b[j]) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
}
cout << dp[n][m];

【算法总结】图论/dp-动态规划 大总结的更多相关文章

  1. 五大常用算法之二:动态规划算法(DP)

    一.基本概念 动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移.一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划. 二.基本思想与策略 基本 ...

  2. (转)dp动态规划分类详解

    dp动态规划分类详解 转自:http://blog.csdn.NET/cc_again/article/details/25866971 动态规划一直是ACM竞赛中的重点,同时又是难点,因为该算法时间 ...

  3. DP动态规划学习笔记——高级篇上

    说了要肝的怎么能咕咕咕呢? 不了解DP或者想从基础开始学习DP的请移步上一篇博客:DP动态规划学习笔记 这一篇博客我们将分为上中下三篇(这样就不用咕咕咕了...),上篇是较难一些树形DP,中篇则是数位 ...

  4. 树形DP——动态规划与数据结构的结合,在树上做DP

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法与数据结构的第15篇,也是动态规划系列的第4篇. 之前的几篇文章当中一直在聊背包问题,不知道大家有没有觉得有些腻味了.虽然经典的文 ...

  5. 算法导论——lec 11 动态规划及应用

    和分治法一样,动态规划也是通过组合子问题的解而解决整个问题的.分治法是指将问题划分为一个一个独立的子问题,递归地求解各个子问题然后合并子问题的解而得到原问题的解.与此不同,动态规划适用于子问题不是相互 ...

  6. Day 5 笔记 dp动态规划

    Day 5 笔记 dp动态规划 一.动态规划的基本思路 就是用一些子状态来算出全局状态. 特点: 无后效性--狗熊掰棒子,所以滚动什么的最好了 可以分解性--每个大的状态可以分解成较小的步骤完成 dp ...

  7. 阿里、腾讯、京东、微软,各家算法&数据挖掘岗位面经大起底!

    阿里.腾讯.京东.微软,各家算法&数据挖掘岗位面经大起底! 2016-02-24 36大数据 36大数据 作者: 江少华 摘要: 从2015年8月到2015年10月,花了3个月时间找工作,先后 ...

  8. DP问题大合集

    引入 动态规划(Dynamic Programming,DP,动规),是求解决策过程最优化的过程.20世纪50年代初,美国数学家贝尔曼(R.Bellman)等人在研究多阶段决策过程的优化问题时,提出了 ...

  9. 玩转算法系列--图论精讲 面试升职必备(Java版)

    第1章 和bobo老师一起,玩转图论算法欢迎大家来到我的新课程:<玩转图论算法>.在这个课程中,我们将一起完整学习图论领域的经典算法,培养大家的图论建模能力.通过这个课程的学习,你将能够真 ...

  10. [算法模版]子序列DP

    [算法模版]子序列DP 如何求本质不同子序列个数? 朴素DP 复杂度为\(O(nq)\).其中\(q\)为字符集大小. \(dp[i]\)代表以第\(i\)个数结尾的本质不同子序列个数.注意,这里对于 ...

随机推荐

  1. 存储过程带参数和sqlcommand

    public DataSet SelectBillNo(string CarrierCode, string Date, string CompanyCode) { System.Collection ...

  2. 从0开发3D引擎(七):学习Reason语言

    目录 上一篇博文 介绍Reason Reason的优势 如何学习Reason? 介绍Reason的部分知识点 大家好,本文介绍Reason语言以及学习Reason的方法. 上一篇博文 从0开发3D引擎 ...

  3. 关于<Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)>看后的一些总结-1

    原文地址:https://www.anquanke.com/post/id/194384#h3-3 1.java rmi 关于rmi客户端和服务端通信的过程,java的方法都实现在rmi服务端,客户端 ...

  4. C++中虚析构的作用

    为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用. 基本概念: 析构函数是用来回收对象的: 虚析构函数是析构函数的一种: 基类是一类对象共有属性的抽象.比如,猫和狗都是动物,都会 ...

  5. 学习 lind api 十月 第一弹

    step one 我们来看一下代码的结构

  6. 20191211 HNOI2017 模拟赛 问题A

    题目: 分析: 好难好难... 下来听神仙讲.. 每一个长度为n-2的prufer序列一一对应一棵大小为n的树... 每个点在序列中的出现次数为该点的度数减一 哦??? ... 哦... prufer ...

  7. springBoot的事件机制---GenericApplicationListener用法

    springBoot的事件机制---GenericApplicationListener用法 什么是ApplicationContext? 它是Spring的核心,Context我们通常解释为上下文环 ...

  8. 在 Ubuntu 上安装 K8S教程

    在 Ubuntu 上安装 K8S教程 1,更新系统源 如果系统本身自带得镜像地址,服务器在国外,下载速度会很慢,可以打开 /etc/apt/sources.lis 替换为国内得镜像源. apt upg ...

  9. Prometheus学习笔记之教程推荐

    最近学习K8S和基于容器的监控,发现了如下的教程质量不错,记录下来以备参考 K8S最佳实战(包括了K8S的Prometheus监控和EFK日志搜集) https://jimmysong.io/kube ...

  10. 3万字总结,Mysql优化之精髓

    本文知识点较多,篇幅较长,请耐心学习 MySQL已经成为时下关系型数据库产品的中坚力量,备受互联网大厂的青睐,出门面试想进BAT,想拿高工资,不会点MySQL优化知识,拿offer的成功率会大大下降. ...