写于一只蹲在角落的蒟蒻-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. NTT - 牛客

    链接:https://www.nowcoder.com/acm/contest/133/D来源:牛客网 题目描述 Applese打开了m个QQ群,向群友们发出了组队的邀请.作为网红选手,Applese ...

  2. Android studio 报错Error:Internal error: (java.lang.ClassNotFoundException) com.google.wireless.android.sdk.stats.IntellijIndexingStats$Index

    Android studio运行make build报错 解决方法 在studio的File-->Settings-->Build, Execution, Deployment---> ...

  3. java8 stream自定义分组求和并排序

    public static void main(String[] args) { List<GroupDetailDTO> list = new ArrayList<>(); ...

  4. HashMap在JDK7和JDK8中的区别

    在[深入浅出集合Map]中,已讲述了HashMap在jdk7中实现,在此就不再细说了 JDK7中的HashMap 基于链表+数组实现,底层维护一个Entry数组 Entry<K,V>[]  ...

  5. Python里使用转义字符\r时遇到的问题

    在Pycharm里使用转义字符\r和在IDLE里使用\r产生的结果是不一样的. 例子如下: print("你好!\r我是Python!") 输出结果为: 我是Python! 前面的 ...

  6. laravel aritisan命令大全

    1常用命令 显示某个命令的帮助 php artisan -h make:controller 实例命令 php artisan make:controller -r Api/TestControlle ...

  7. 求LCM(a,b)=n的(a,b)的总对数(a<=b)

    \(a={p_1} ^ {a_1} *{p_1} ^ {a_1} *..........*{p_n} ^ {a_n}\) \(b={p_1} ^ {b_1} *{p_1} ^ {b_1} *..... ...

  8. 从O365中获取users到D365中 使用flow

    在我上篇blog中讲解到了怎么用代码把O365 users 获取到D365中. 从O365中获取users到D365中 这几天一直在研究flow, 发现flow可以更简单的完成这个功能. 一开始没有考 ...

  9. Web 开发工具类(2): HttpClientUtils

    HttpClientUtils 整合了一些 web开发中常用的httpClient操作: package com.evan.common.utils; import java.io.IOExcepti ...

  10. CentOS 7 上安装 Django 2.2.4,解决报错:No module named ‘_sqlite3′

    1.首先下载最新版的sqlite :https://www.sqlite.org/download.html 下载源码包: 配置和编译方法如下: ./configure --prefix=/usr/l ...