【算法总结】图论/dp-动态规划 大总结
写于一只蹲在角落的蒟蒻-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-动态规划 大总结的更多相关文章
- 五大常用算法之二:动态规划算法(DP)
一.基本概念 动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移.一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划. 二.基本思想与策略 基本 ...
- (转)dp动态规划分类详解
dp动态规划分类详解 转自:http://blog.csdn.NET/cc_again/article/details/25866971 动态规划一直是ACM竞赛中的重点,同时又是难点,因为该算法时间 ...
- DP动态规划学习笔记——高级篇上
说了要肝的怎么能咕咕咕呢? 不了解DP或者想从基础开始学习DP的请移步上一篇博客:DP动态规划学习笔记 这一篇博客我们将分为上中下三篇(这样就不用咕咕咕了...),上篇是较难一些树形DP,中篇则是数位 ...
- 树形DP——动态规划与数据结构的结合,在树上做DP
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法与数据结构的第15篇,也是动态规划系列的第4篇. 之前的几篇文章当中一直在聊背包问题,不知道大家有没有觉得有些腻味了.虽然经典的文 ...
- 算法导论——lec 11 动态规划及应用
和分治法一样,动态规划也是通过组合子问题的解而解决整个问题的.分治法是指将问题划分为一个一个独立的子问题,递归地求解各个子问题然后合并子问题的解而得到原问题的解.与此不同,动态规划适用于子问题不是相互 ...
- Day 5 笔记 dp动态规划
Day 5 笔记 dp动态规划 一.动态规划的基本思路 就是用一些子状态来算出全局状态. 特点: 无后效性--狗熊掰棒子,所以滚动什么的最好了 可以分解性--每个大的状态可以分解成较小的步骤完成 dp ...
- 阿里、腾讯、京东、微软,各家算法&数据挖掘岗位面经大起底!
阿里.腾讯.京东.微软,各家算法&数据挖掘岗位面经大起底! 2016-02-24 36大数据 36大数据 作者: 江少华 摘要: 从2015年8月到2015年10月,花了3个月时间找工作,先后 ...
- DP问题大合集
引入 动态规划(Dynamic Programming,DP,动规),是求解决策过程最优化的过程.20世纪50年代初,美国数学家贝尔曼(R.Bellman)等人在研究多阶段决策过程的优化问题时,提出了 ...
- 玩转算法系列--图论精讲 面试升职必备(Java版)
第1章 和bobo老师一起,玩转图论算法欢迎大家来到我的新课程:<玩转图论算法>.在这个课程中,我们将一起完整学习图论领域的经典算法,培养大家的图论建模能力.通过这个课程的学习,你将能够真 ...
- [算法模版]子序列DP
[算法模版]子序列DP 如何求本质不同子序列个数? 朴素DP 复杂度为\(O(nq)\).其中\(q\)为字符集大小. \(dp[i]\)代表以第\(i\)个数结尾的本质不同子序列个数.注意,这里对于 ...
随机推荐
- 一个命令解决linux重启nginx就丢失pid文件问题
sudo nginx -c /etc/nginx/nginx.conf
- [bzoj4443] [loj#2006] [洛谷P4251] [Scoi2015]小凸玩矩阵
Description 小凸和小方是好朋友,小方给小凸一个 \(N \times M\)( \(N \leq M\) )的矩阵 \(A\) ,要求小秃从其中选出 \(N\) 个数,其中任意两个数字不能 ...
- xlwings excel(四)
前言 当年看<别怕,Excel VBA其实很简单>相见恨晚,看了第一版电子版之后,买了纸质版,然后将其送人.而后,发现出了第二版,买之收藏.之后,发现Python这一编程语言,简直是逆天, ...
- [校内训练19_09_10]sort
题意 给一个非负整数序列,每次问能否异或上一个正整数使得所有的数单调不减.如果能,输出最小的x,否则输出-1.单点修改.多测.要求最多一个log. 思考 只要考虑相邻的两个数.找到这两个数最高的不同的 ...
- android开发实战-记账本APP(一)
记账本开发流程: 对于一个记账本的初步开发而言,我实现的功能有: ①实现一个记账本的页面 ②可以添加数据并更新到页面中 ③可以将数据信息以图表的形式展现 (一)首先,制作一个记账本的页面. ①在系统自 ...
- 使用AOP和Semaphore对项目中具体的某一个接口进行限流
整体思路: 一 具体接口,可以自定义一个注解,配置限流量,然后对需要限流的方法加上注解即可! 二 容器初始化的时候扫描所有所有controller,并找出需要限流的接口方法,获取对应的限流量 三 使用 ...
- linux之samba使用
工作中,很多时候,我导出文件,或者上传文件的时候经常失败,报samba fail,但我并不知道samba是干什么用的,也老是听同事说什么samba没有挂载,但我基本上不知道什么是samba,更不要说什 ...
- IntelliJ IDEA 2019.3 安装+永久破解[Windows]
IntelliJ IDEA 2019的最后一个版本发布了,听说大幅优化了运行速度,本人实测启动速度确实比以前快不少,所以赶紧安排上新版本IDEA的破解教程 系统环境:Win10 LTSC(1809) ...
- 五、spring源码阅读之ClassPathXmlApplicationContext加载beanFactory
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml&q ...
- 实验13:VLAN/TRUNK/VTP/
实验10-1: 划分VLAN Ø 实验目的通过本实验,读者可以掌握如下技能:(1) 熟悉VLAN 的创建(2) 把交换机接口划分到特定VLAN Ø 实验拓扑 实验步骤要配置VLAN,首先要 ...