写于一只蹲在角落的蒟蒻-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. spark和strom优劣分析

    对于Storm来说:1.建议在那种需要纯实时,不能忍受1秒以上延迟的场景下使用,比如实时金融系统,要求纯实时进行金融交易和分析2.此外,如果对于实时计算的功能中,要求可靠的事务机制和可靠性机制,即数据 ...

  2. 程序员Java架构师多线程面试题和回答解析

    当我们在Java架构师面试的过程中常见的多线程和并发方面的问题肯定是必不可少的一部分.那么在面试之前我们更应该多准备一些关于多线程方面的问题. 面试官只是想确信面试者有足够的Java线程与并发方面的知 ...

  3. 夜晚 暴力 十点钟 jQuery 的 extend 实现 原理

    jQuery 的 extend 是怎么实现的 一看代码 喉咙 就不爽 这是 jQuery JavaScript Library v1.6 一看代码 多的 屁股疼 , 排名 前三 的 文章 可以去参考下 ...

  4. nginx default setting

    # You may add here your# server {#    ...# }# statements for each of your virtual hosts to this file ...

  5. Dynamics CRM Tips

    这里是作为开发中遇到的各式各样的问题的总结贴. 如果对Dynamics CRM 开发有兴趣, 请参考Step by Step 开发dynamics CRM 移除sitemap中的entity 从O36 ...

  6. 使用Rclone和WinFsp挂载FTP为磁盘

    介绍 Rclone:是一款的命令行工具,支持在不同对象存储.网盘间同步.上传.下载数据.官网网址:rclone.org WinFsp:是一款Windows平台下的文件系统代理软件(Windows Fi ...

  7. Java 8 Stream流编程学习

    本文是自己学习菜鸟教程中总结的笔记,用于快速找代码,完整的文档见菜鸟教程:Java 8 Stream Stream 使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达 ...

  8. LeetCode 11 水池蓄水问题

    今天给大家分享的是一道LeetCode中等难度的题,难度不大,但是解法蛮有意思.我们一起来看题目: Link Container With Most Water Difficulty Medium 题 ...

  9. Kivy Event 在哪里?

    kivy 程序都是都需要继承APP基类 而App类在Lib\site-packages\kivy\app.py文件定义 from kivy.event import EventDispatcher c ...

  10. 使用H5与webGL的3D 可视化地铁展示

    前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCAD ...