【学习笔记】有向无环图上的DP

手动博客搬家: 本文发表于20180716 10:49:04, 原地址https://blog.csdn.net/suncongbo/article/details/81061378

首先,感谢以下几位大佬们在此问题上对我的帮助:本市大佬sdqd01, 外省大佬ez_dc, coconight, szlhx01, jxgz03 (均为某OJ用户名)

一、基本概念

有向无环图 (Directed Acyclic Graph, DAG): 没有环的有向图。
Tarjan算法缩点、拓扑排序
在有向无环图上,可以进行动态规划来求解问题,具体见后面的例题。

二、问题引入

一切都要从半年前说起:
半年前我正在准备地理生物中考,其中生物有这样一种题:
给一个食物网(当然是DAG啦),求该食物网里一共有几条食物链。
当时同学们的做法是:枚举每一条食物链。
先不考虑是否符合生物学原理,最坏情况下的复杂度?
\(O(n2^{n-2})\) (把边全连满)

但是我们可以\(DP\)!
我的做法:令\(dp[i]\)表示以\(i\)结束(或称为在\(i\)处汇聚)的食物链条数。(食物链都是从被捕食者向捕食者连有向边)
则转移为:\(dp[i]=\sum_{j\in ind[i]}dp[j]\), 其中\(ind[i]\)为连向\(i\)的点的集合。
初始状态:\(dp[生产者]=1\), 生产者即入度为0的点。
答案:\(ans=\sum dp[最高级消费者]\),最高级消费者即出度为0的点。
但是一个问题是:如果我们用代码实现这个过程,如何确定dp的顺序?
很显然,一个点的\(dp\)值能够被确定,其先决条件是它的所有入点的\(dp\)值都已被确定。因此我们需要确定一个点的排列,使得每个点的所有入点都在这个点之前出现。这里用到拓扑排序。拓扑序就是我们\(DP\)的顺序。
那这样的话,先拓扑排序,记下来拓扑序列,然后从前往后DP?
其实还不用。我们可以直接一边拓扑排序一边DP,就是每\(BFS\)访问到一个节点\(i\),我们枚举\(i\)的所有出度,对于\(i\)的一个出度\(j\), 删掉\(i\)到\(j\)的边 (拓扑排序)同时用\(dp[i]\)更新\(dp[j]\) (DP).
一边拓扑排序,一边dp,既省空间又省代码。
复杂度?\(n\)个点\(m\)条边的话,\(O(n+m)\).
现在,假定这个图一定是DAG. 对于不是DAG的情况,将在后面讨论。

三、例题
  1. codeforces 919D (http://codeforces.com/problemset/problem/919/D)
    https://blog.csdn.net/suncongbo/article/details/81061500

  2. bzoj 1924 (https://www.lydsy.com/JudgeOnline/problem.php?id=1924)
    首先,这道题难点在建图,这个过程不是本文的重点,不再赘述。
    假设我们已经建出了图。现在我们需要求图上的最长链。有以下三种方法:

(1) SPFA跑最长路。 (2) DP. 有BFS和DFS两种方法。
DP做法(BFS):令\(dp[i]\)表示到i为止最长链的长度,则有\(dp[i]=\max_{j\in ind[i]} dp[j]+1\),按拓扑序转移即可。
等等?建出的图上可能有环?
此时答案显然不能是\(\inf\), 因为在本题中走过一个点多次只会统计一次答案。所以我们必须通过Tarjan算法来缩点,将每个强连通分量缩成一个点,点的权值为强连通分量的大小。这样的话,如果题目里的Henry沿最长链走到了这个强连通分量中的一个点,则此时如果顺道把这个强连通分量访问遍,答案显然不会更差。缩完点之后,变成了\(DAG\),然后就可以\(DP\)啦。
但是!除了BFS,我们还有一种更简单的\(DP\)方法,即\(DFS\)!时间复杂度一样,BFS20行左右,DFS不到10行,而且省一点空间
具体做法:令\(dp[i]\)为从i 开始的最长链。代码如下:

void dfs(int u,int prv)
{if(dp[u]-a[u]>0) return;dp[u] = a[u];for(int i=fe0[u]; i; i=e0[i].nxt){dfs(e0[i].v,u);dp[u] = max(dp[u],dp[e0[i].v]+a[u]);}
}

是不是特别简洁!不需要冗长的拓扑排序!
但是它是如何保证转移顺序的呢?
由于是从i开始,因此转移顺序应是每个点的所有出边连向的点转移后才转移这个点,而代码中的for循环恰恰保证了这一点。
整道题代码:(dfs)

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;const int N = 300000;
const int M = 1200000;
const int C = 1000000;
struct Edge
{int v,nxt; bool us;
} e[M+2],e0[M+2];
struct Node
{int x,y,z;bool operator <(const Node &arg) const{return y<arg.y;}
} nd[N+2];
int fe[N+2],fe0[N+2];
int f[3][C+2];
int id1[N+2],id2[N+2];
int dfn[N+2],low[N+2];
int sta[N+2];
bool ins[N+2];
int clr[N+2];
int a[N+2];
int ind[N+2];
int que[N+2];
int dp[N+2];
vector<int> v1,v2;
int n,m,m0,num,cnt,tp,r,c;void addedge(int u,int v)
{m++; e[m].v = v;e[m].nxt = fe[u]; fe[u] = m;
}void addedge0(int u,int v)
{m0++; e0[m0].v = v;e0[m0].nxt = fe0[u]; fe0[u] = m0;ind[v]++;
}int getid1(int x)
{return lower_bound(v1.begin(),v1.end(),x)-v1.begin()+1;
}int getid2(int x)
{return lower_bound(v2.begin(),v2.end(),x)-v2.begin()+1;
}void Tarjan(int u)
{cnt++; dfn[u] = cnt; low[u] = cnt; ins[u] = true;tp++; sta[tp] = u;for(int i=fe[u]; i; i=e[i].nxt){if(dfn[e[i].v]==0) {Tarjan(e[i].v); low[u] = min(low[u],low[e[i].v]);}else if(ins[e[i].v]) {low[u] = min(low[u],dfn[e[i].v]);}}if(low[u]==dfn[u]){num++; a[num] = 1;while(sta[tp]!=u){ins[sta[tp]] = false;clr[sta[tp]] = num;a[num]++;tp--;}ins[u] = false; clr[u] = num; tp--;}
}void dfs(int u,int prv)
{if(dp[u]-a[u]>0) return;dp[u] = a[u];for(int i=fe0[u]; i; i=e0[i].nxt){if(e0[i].v==prv) continue;dfs(e0[i].v,u);dp[u] = max(dp[u],dp[e0[i].v]+a[u]);}
}int main()
{scanf("%d%d%d",&n,&r,&c); m = 0;for(int i=1; i<=n; i++){scanf("%d%d%d",&nd[i].x,&nd[i].y,&nd[i].z);v1.push_back(nd[i].x); v2.push_back(nd[i].y);}sort(nd+1,nd+n+1);int cur = 0,nxt = 1,prv = -1,pcur = 1,pnxt = 1,pprv = 0;while(nd[pnxt].y==1) {f[nxt][nd[pnxt].x] = pnxt; pnxt++;}for(int i=1; i<=c; i++){cur = (cur+1)%3; nxt = (nxt+1)%3; prv = (prv+1)%3;while(pnxt<=n && nd[pnxt].y==i+1){f[nxt][nd[pnxt].x] = pnxt;pnxt++;}while(nd[pcur].y==i){if(nd[pcur].z==3){if(f[prv][nd[pcur].x]>0) addedge(pcur,f[prv][nd[pcur].x]);if(f[prv][nd[pcur].x-1]>0) addedge(pcur,f[prv][nd[pcur].x-1]);if(f[prv][nd[pcur].x+1]>0) addedge(pcur,f[prv][nd[pcur].x+1]);if(f[cur][nd[pcur].x-1]>0) addedge(pcur,f[cur][nd[pcur].x-1]);if(f[cur][nd[pcur].x+1]>0) addedge(pcur,f[cur][nd[pcur].x+1]);if(f[nxt][nd[pcur].x]>0) addedge(pcur,f[nxt][nd[pcur].x]);if(f[nxt][nd[pcur].x-1]>0) addedge(pcur,f[nxt][nd[pcur].x-1]);if(f[nxt][nd[pcur].x+1]>0) addedge(pcur,f[nxt][nd[pcur].x+1]);}pcur++;}while(nd[pprv].y==i-1){f[prv][nd[pprv].x] = 0;pprv++;}}sort(v1.begin(),v1.end()); v1.erase(unique(v1.begin(),v1.end()),v1.end());sort(v2.begin(),v2.end()); v2.erase(unique(v2.begin(),v2.end()),v2.end());for(int i=1; i<=n; i++){nd[i].x = getid1(nd[i].x); nd[i].y = getid2(nd[i].y);if(nd[i].z==1) id1[nd[i].x] = i; else if(nd[i].z==2) id2[nd[i].y] = i;}for(int i=1; i<=n; i++){if(id1[nd[i].x]!=i) addedge(id1[nd[i].x],i);if(id2[nd[i].y]!=i) addedge(id2[nd[i].y],i);if(nd[i].z==1 && id1[nd[i].x]!=i) addedge(i,id1[nd[i].x]);else if(nd[i].z==2 && id2[nd[i].y]!=i) addedge(i,id2[nd[i].y]);}cnt = 0; num = 0; for(int i=1; i<=n; i++) if(dfn[i]==0) Tarjan(i);m0 = 0;for(int i=1; i<=n; i++){for(int j=fe[i]; j; j=e[j].nxt){if(clr[i]!=clr[e[j].v]){addedge0(clr[i],clr[e[j].v]);}}}int ans = 0;for(int i=1; i<=num; i++) if(ind[i]==0) {dfs(i,0); ans = max(ans,dp[i]);}printf("%d\n",ans);return 0;
}
  1. bzoj 1179 (https://www.lydsy.com/JudgeOnline/problem.php?id=1179)
    https://blog.csdn.net/suncongbo/article/details/81061601
发表于 2019-01-16 13:01 suncongbo 阅读(...) 评论(...) 编辑 收藏

刷新评论刷新页面返回顶部

【学习笔记】有向无环图上的DP相关推荐

  1. 笔记:深度学习与有向无环图SVM结合用于年龄估计的局部调整

    阅读论文:Combined Deep Learning With Directed Acyclic Graph SVM for Local Adjustment of Age Estimation | ...

  2. 【算法学习笔记】85.破环为链 序列DP 松弛+代价 SJTU OJ 1073 能量项链

    和石子合并很像, 为了对环状进行处理, 我们可以把输入数据复制一份接连在后边. 这样在最后的结果枚举起点找最大即可. 注意这里代价的计算, 因为我们的data[i]只记录了珠子的头 , 珠子的尾部即是 ...

  3. HDU1524(博弈--有向无环图SG函数)

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1524 题意:在一个有向无环图上有n个顶点,每一个顶点都只有一个棋子,有两个人,每次根据这个图只能将任意一颗 ...

  4. Spark构建DAG(有向无环图)

    文章目录 前言 一.构建RDD有向无环图 二.RDD有向无环图拆分 三. DAGScheduler 四. Task调度 五. Task执行 前言 有向无环图(Directed Acyclic Grap ...

  5. 48. 数据结构笔记之四十八的有向无环图的应用关键路径

    48. 数据结构笔记之四十八的有向无环图的应用关键路径 "富贵不淫贫贱乐 , 男儿到此是豪雄.-- 程颢" 来看下有向无环图的另一个应用关键路径. 1.  关键路径 与AOV-网相 ...

  6. 【数据结构学习记录22】——有向无环图及其应用

    有向无环图及其应用 一.有向无环图的概念 二.拓扑排序(AOV网) 1.概念 2.偏序与全序 a).偏序 b).全序 c).偏序与全序的区别 3.拓扑有序 4.拓扑排序的过程 三.关键路径(AOE网) ...

  7. 图学习笔记(一):图

    图学习笔记(一):图与图学习 一. 图是什么? 1. 图的定义 2. 图的基本表示方法及概念 2.1 图的构成 2.2 图的概念 3. 例子:空手道俱乐部图 二. 如何存储图?存储图的方式:三种 第一 ...

  8. 有向无环图(DAG)的温故知新

    当我们学习数据结构的时候,总是觉得很枯燥,而当我们解决实际问题的时候,又往往因为对数据结构了解的匮乏而束手无策.从问题中来,到问题中去,在某一点上的深入思考并且不断的实践积累,或许是个笨办法,但笨办法 ...

  9. C#实现有向无环图(DAG)拓扑排序

    对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在 ...

最新文章

  1. 深度学习面临天花板,亟需更可信、可靠、安全的第三代AI技术|AI ProCon 2019
  2. git branch用法总结
  3. Linux多线程编程----IO【select、poll、epoll】
  4. 织梦 m list.php tid,在织梦标签dede:list中增加noflag属性的方法支持5.7版本
  5. jquery 验证控件
  6. java的常见异常与错误总结
  7. [EntLib]微软企业库5.0 学习之路——第七步、Cryptographer加密模块简单分析、自定义加密接口及使用—上篇...
  8. 快速掌握MATLAB应用,从这一步开始
  9. 牛客小白月赛12:月月给华华出题(欧拉函数)
  10. 那些开发《虚拟光驱》的人们
  11. mybatis-generator配置流程(详细) 2021-05-15
  12. SpringCloud系列第09节之消息总线Bus
  13. wcf、web api、webservicer 之间的区别
  14. 关于统计分析软件Spss统计个案数和实际数据的个案数不一致问题
  15. 应用程序正常初始化失败(0xc0000135)
  16. bzoj-1270 [BeijingWc2008]雷涛的小猫
  17. FAST迅捷路由器设置
  18. 计算机插上u盘就无法点亮,u盘启动电脑无反应,小编教你电脑插上U盘后无法启动解决方法...
  19. 怎么借书(分配资源问题)(SWUST OJ1287)
  20. LINQS、xp、xb等开源项目的来龙去脉。

热门文章

  1. google设置在新标签页打开的方法
  2. pyinstaller 编译完exe执行结果 ModuleNotFoundError: No module named ‘sqlalchemy‘
  3. python处理rgb_如何在Python中读取给定像素的RGB值?
  4. Delphi 中自定义异常及异常处理的一般方法
  5. DNS抓包分析--wireshark
  6. 程序改错(递归函数):数字转字符
  7. Spring AOP进行日志记录,管理
  8. Shrio Unable to execute ‘doFinal‘ with cipher instance
  9. 一站式导航 -- 奋斗の博客
  10. 手把手带你撸深度学习经典模型(一)----- UNet