zoukankan      html  css  js  c++  java
  • 【学习笔记】有向无环图上的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_{jin 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_{jin 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
    ",ans);
    	return 0;
    }
    
    1. bzoj 1179 (https://www.lydsy.com/JudgeOnline/problem.php?id=1179)
      https://blog.csdn.net/suncongbo/article/details/81061601
  • 相关阅读:
    UVALive 7509 Dome and Steles
    HDU 5884 Sort
    Gym 101194H Great Cells
    HDU 5451 Best Solver
    HDU 5883 The Best Path
    HDU 5875 Function
    卡特兰数
    UVa 11729 Commando War 突击战
    UVa 11292 The Dragon of Loowater 勇者斗恶龙
    Spark Scala Flink版本对应关系
  • 原文地址:https://www.cnblogs.com/suncongbo/p/10276426.html
Copyright © 2011-2022 走看看