博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【学习笔记】有向无环图上的DP
阅读量:4672 次
发布时间:2019-06-09

本文共 5013 字,大约阅读时间需要 16 分钟。

手动博客搬家: 本文发表于20180716 10:49:04, 原地址

首先,感谢以下几位大佬们在此问题上对我的帮助:本市大佬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 ()

  2. bzoj 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
#include
#include
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
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.cnblogs.com/suncongbo/p/10276426.html

你可能感兴趣的文章
[Algorithm] Find first missing positive integer
查看>>
[Angular] @ViewChild and template #refs to get Element Ref
查看>>
[Angular] Show a loading indicator in Angular using *ngIf/else, the as keyword and the async pipe
查看>>
[Angular] Configurable Angular Components - Content Projection and Input Templates
查看>>
[PWA] 17. Cache the photo
查看>>
[RxJS] ReplaySubject with buffer
查看>>
[Firebase] 3. Firebase Simple Login Form
查看>>
AI 线性代数
查看>>
ltp-ddt realtime_cpu_load涉及的cyclictest 交叉编译
查看>>
MySQL中Checkpoint技术
查看>>
【MT】牛津的MT教程
查看>>
Meta标签中的format-detection属性及含义
查看>>
PowerDesigner教程系列(四)概念数据模型
查看>>
DataGradView操作之,列头右键菜单隐藏和显示字段功能
查看>>
windows中使用Git工具连接GitHub(配置篇)
查看>>
示例 - 10行代码在C#中获取页面元素布局信息
查看>>
Linux 发行版本简介 (zz)
查看>>
POJ 2387 Til the Cows Come Home(Dijkstra)
查看>>
关于使用Tomcat服务器出现413错误的解决办法(Request Entity Too Large)
查看>>
离线使用iPhone SDK文档的方法
查看>>