(」・ω・)」うー!(/・ω・)/にゃー!
——潜行吧奈亚子

DAG

什么是DAG?

DAG (\(Directed\) \(Acycled\) \(Graph\))即 有向无环图,是一种 介于树与有向有环图之间的数据结构,是一种非常重要的工具。

DAG的性质

  • 可以在DAG上实现动态规划
  • 在处理关于DAG的问题时,需要依次处理每个点,在对一个点进行处理时,需要保证每一个指向它的点都已处理过
  • DAG是具有层次关系
  • 有向无环图中必定有至少一个入度为 \(0\) 的点

证明:假设有一个有向无环图中没有入度为 \(0\) 的点,则对于每个点必定有一个点指向当前点,不管如何必定会形成闭环,与有向无环的前提相违背,故有向无环图中必定有至少一个入度为 \(0\) 的点。

代码实现

思路

先将入度为 \(0\) 的点入队,然后进行操作,然后将队首元素删除,顺便把自队首元素出来的边删掉,再次遍历是否有入度为 \(0\) 的点,再入队,再出队……以此类推。

代码

#include<bits/stdc++.h>
using namespace std;
vector<int> e[100001];//邻接表存图 
int n,m,u,v,ind[100001];//ind[i]为i的入度
void tra(){//遍历图中点 
	queue<int> q;//用来存当前入度为0的点 
	for(int i=1;i<=n;i++)//初始入度为0的点(无前置点)先加入 
		if(ind[i]==0)q.push(i);
	while(!q.empty()){//只要队列非空 
		int h=q.front();
		cout<<h<<" ";
		q.pop();//队首出队
		//删除队首,队首指向的点入度减1,如果入度变为0就入队 
		for(int i=0;i<e[h].size();i++)
			if(--ind[e[h][i]]==0)q.push(e[h][i]);
	}
} 
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>u>>v;
		e[u].push_back(v);//注意是有向图 
		ind[v]++;//v的入度加1 
	}
	tra();
	return 0;
} 

例题

1.DAG最长路

给定一个 \(n\) 个点,\(m\) 个边的有向无环图,每次给出 \(u,v,dist\),表示 \(u,v\) 间有一条长度为 \(dist\) 的边(方向为\(u\rightarrow v\)),请求出图中的最长的一条路径。

法一

考虑动态规划,\(dp[i]\) 为到 \(i\) 结束的最长路径长。

代码如下:

#include<bits/stdc++.h>
using namespace std;
struct Edge{
	int to,val;
};
vector<Edge> e[1000001];//邻接表存图 
int n,m,u,v,t,ans,ind[1000001];//ind[i]为i的入度
int dp[1000005]={0};//dp[i]为到达i的最长路 
void tra(){//遍历图中点 
	queue<int> q;//用来存当前入度为0的点 
	for(int i=1;i<=n;i++)//初始入度为0的点(无前置点)先加入 
		if(ind[i]==0){
			q.push(i);//入度为0的点 
			dp[i]=0;//初始化为0 
		}
	while(!q.empty()){//只要队列非空 
		int h=q.front();
		q.pop();//队首出队
		ans=max(dp[h],ans);
		//删除队首,队首指向的点入度减1,如果入度变为0就入队 
		for(int i=0;i<e[h].size();i++){
			if(--ind[e[h][i].to]==0)q.push(e[h][i].to);
			dp[e[h][i].to]=max(dp[e[h][i].to],dp[h]+e[h][i].val);
			//h点确定好了,用h点更新指向的每个点的答案 
		}	
	}
} 
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>u>>v>>t;
		e[u].push_back((Edge){v,t});//注意是有向图 
		ind[v]++;//v的入度加1 
	}
	tra();
	cout<<ans;
	return 0;
} 

法二

\(dp[i]\) 为从 \(i\) 出发的最长路径,使用记忆化优化

#include<bits/stdc++.h>
using namespace std;
struct Edge{
	int to,val;
};
vector<Edge> e[1000001];//邻接表存图 
int n,m,u,v,t,ans;
int dp[1000001]={0};//dp[i]为从i出发的最长路 
int srh(int k){//返回从k出发的最长路线
	if(dp[k]>0)return dp[k];//记忆化 
	dp[k]=0;//初始为0;
	for(int i=0;i<e[k].size();i++)//通过直接通向e[k][i].to 
		dp[k]=max(dp[k],srh(e[k][i].to)+e[k][i].val);
	return dp[k];
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>u>>v>>t;
		e[u].push_back((Edge){v,t});//注意是有向图
	}
	for(int i=1;i<=m;i++)ans=max(ans,srh(i)); 
	cout<<ans;
	return 0;
} 

2.最大食物链计数

仍然考虑动态规划,\(dp[i]\) 为从 \(i\) 出发,到出度为 \(0\) 的点的方法数。从出度为0的点往回递归,计算总方法数。

#include<bits/stdc++.h>
using namespace std;
vector<int> e[1000001];//邻接表存图 
int n,m,u,v,t,ans,ind[1000001];
int dp[1000001]={0};//dp[i]为从i出发的最长路 
int srh(int k){//返回从k出发的最长路线
	if(dp[k]>0)return dp[k];//记忆化 
	if(!e[k].size())return dp[k]=1;//出度为0的点 
	dp[k]=0;//初始为0;
	for(int i=0;i<e[k].size();i++)//通过直接通向e[k][i].to 
		dp[k]=(dp[k]+srh(e[k][i]))%80112002;
	return dp[k];
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>u>>v;
		e[u].push_back(v);//注意是有向图
		ind[v]++;
	}
	for(int i=1;i<=n;i++)
		if(!ind[i])ans=(ans+srh(i))%80112002;//只对符合题意的起点 
	cout<<ans;
	return 0;
} 

3.图上判环

给定一个 \(n\) 个点,\(m\) 个边的有向图,判断图中是否存在一个环。如果存在,则输出"Yes",否则输出"No"。给出 \(m\)\(u,v\) 表示 \(u,v\)间有一条有向边(\(u\rightarrow v\))

只需使用 \(cnt\) 记录入度为 \(0\) 的点(遍历过程中入度变成 \(0\) 的也算),如果 \(cnt=n\) 就代表无环。

#include<bits/stdc++.h>
using namespace std;
vector<int> e[1000001];//邻接表存图 
int n,m,u,v,t,cnt,ans,ind[1000001];//删除了cnt个 
int dp[1000001]={0};//dp[i]为从i出发的最长路 
void tra(){
	queue<int> q;
	for(int i=1;i<=n;i++)
		if(!ind[i])q.push(i);
	while(!q.empty()){
		int h=q.front();
		q.pop(),cnt++;
		for(int i=0;i<e[h].size();i++)
			if(--ind[e[h][i]]==0)q.push(e[h][i]);
	} 
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>u>>v;
		e[u].push_back(v);//注意是有向图
		ind[v]++;
	}
	tra();
	if(cnt==n)cout<<"No";//无环
	else cout<<"Yes";
	return 0;
} 

一些值得注意的地方

  1. 上述算法复杂度大多是 \(O(m)\)\(m\) 为边数)
  2. 每次只能处理队列中的点(编号小的优先)
posted @ 2022-03-20 11:35  GalaxyOier  阅读(344)  评论(0)    收藏  举报