手动博客搬家: 本文发表于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

【学习笔记】有向无环图上的DP的更多相关文章

  1. HDU 3249 Test for job (有向无环图上的最长路,DP)

     解题思路: 求有向无环图上的最长路.简单的动态规划 #include <iostream> #include <cstring> #include <cstdlib ...

  2. CSU 1804 - 有向无环图 - [(类似于)树形DP]

    题目链接:http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1804 Bobo 有一个 n 个点,m 条边的有向无环图(即对于任意点 v,不存在从点 ...

  3. [笔记] 有向无环图 DAG

    最小链覆盖 (最长反链) 最小链覆盖 \(=n-\) 最大匹配. 考虑首先每个点自成一条链,此时恰好有 \(n\) 条链,最终答案一定是合并(首尾相接)若干条链形成的. 将两点匹配的含义其实就是将链合 ...

  4. UVA_1025 a Spy in the Metro 有向无环图的动态规划问题

    应当认为,有向无环图上的动态规划问题是动态规划的基本模型之一,对于某个模型,如果可以转换为某一有向无环图的最长.最短路径问题,则可以套用动态规划若干方法解决. 原题参见刘汝佳紫薯267页. 在这个题目 ...

  5. PGM学习之六 从有向无环图(DAG)到贝叶斯网络(Bayesian Networks)

    本文的目的是记录一些在学习贝叶斯网络(Bayesian Networks)过程中遇到的基本问题.主要包括有向无环图(DAG),I-Maps,分解(Factorization),有向分割(d-Separ ...

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

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

  7. JavaScript + SVG实现Web前端WorkFlow工作流DAG有向无环图

    一.效果图展示及说明 (图一) (图二) 附注说明: 1. 图例都是DAG有向无环图的展现效果.两张图的区别为第二张图包含了多个分段关系.放置展示图片效果主要是为了说明该例子支持多段关系的展现(当前也 ...

  8. 算法精解:DAG有向无环图

    DAG是公认的下一代区块链的标志.本文从算法基础去研究分析DAG算法,以及它是如何运用到区块链中,解决了当前区块链的哪些问题. 关键字:DAG,有向无环图,算法,背包,深度优先搜索,栈,BlockCh ...

  9. 第十二届湖南省赛 (B - 有向无环图 )(拓扑排序+思维)好题

    Bobo 有一个 n 个点,m 条边的有向无环图(即对于任意点 v,不存在从点 v 开始.点 v 结束的路径). 为了方便,点用 1,2,…,n 编号. 设 count(x,y) 表示点 x 到点 y ...

随机推荐

  1. hibernate实战笔记1---初探

    因为在学习Spring的时候学到有关数据库的章节的时候,提及到了hibernate的集成,可是我对hibernate技术差点儿是一点不了解.仅仅是知道它是一个orm对象映射框架,所以在初探的章节做一下 ...

  2. Spring+Mybatis之注册功能demo

    这次先注册功能的是基于登录之后,所以很多配置,实体类等就不再赘述了. 首先也不是直接在地址栏输入一个网页就可以到注册页面的.而是需要通过后台发送一个请求从而跳转到注册页面 先写注册页面,body部分 ...

  3. hdu 1754(单点更新 ,区间最大值)

    I Hate It Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total S ...

  4. poj--1101--The Game(bfs)

    The Game Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 9746   Accepted: 2967 Descript ...

  5. linux下如何使用sftp命令进行文件上传和下载

    sftp 是一个交互式文件传输程式.它类似于 ftp, 但它进行加密传输,比FTP有更高的安全性.下边就简单介绍一下如何远程连接主机,进行文件的上传和下载,以及一些相关操作. 举例,如远程主机的 IP ...

  6. 什么是 less? 如何使用 less?

    什么是 Less? Less 是一门 CSS 预处理语言,它扩充了 CSS 语言,增加了诸如变量.混合(mixin).嵌套.函数等功能,让 CSS 更易编写.维护等. 本质上,Less 包含一套自定义 ...

  7. Necklace of Beads(polya定理)

    http://poj.org/problem?id=1286 题意:求用3种颜色给n个珠子涂色的方案数.polya定理模板题. #include <stdio.h> #include &l ...

  8. Github标星4W+,热榜第一,如何用Python实现所有算法

    文章发布于公号[数智物语] (ID:decision_engine),关注公号不错过每一篇干货. 来源 | 大数据文摘(BigDataDigest) 编译 | 周素云.蒋宝尚 学会了 Python 基 ...

  9. Elasticsearch之curl删除

    扩展下, Elasticsearch之curl删除索引库 [hadoop@djt002 elasticsearch-2.4.3]$ curl -XDELETE 'http://192.168.80.2 ...

  10. Windows 环境下 Docker 使用及配置

    原文引用: https://www.cnblogs.com/moashen/p/8067612.html 我们可以使用以下两种方式在Windows环境下使用docker: 1. 直接安装: Docke ...