acwing164

给定一张 N 个点 M 条边的有向无环图,分别统计从每个点出发能够到达的点的数量。

输入格式
第一行两个整数 N,M,接下来 M 行每行两个整数 x,y,表示从 x 到 y 的一条有向边。

输出格式
输出共 N 行,表示每个点能够到达的点的数量。

数据范围
1≤N,M≤30000
输入样例:
10 10
3 8
2 3
2 5
5 9
5 9
2 3
3 9
4 8
2 10
4 9
输出样例:
1
6
3
3
2
1
1
1
1
1


这道题的关键是得出一个递推公式。假设由节点u指向的节点为\(v_1,v_2,...,v_n\),从节点u可达的节点的集合为为\(s(u)\)。则不难写出递推公式:
\(s(u)=\{u\}\cup_{i=1}^{n} s(v_i)\)
根据递推公式,我们需要对每个节点都使用一个结合储存其到所有节点的可达状态。若u到v可达,则集合中对应元素置1,否则置0。由于最多可能有30000个节点,所以如果用int数组可能会超出限制内存,所以使用状态压缩的方法。使用stl的bitset容器储存01状态,使用位运算|来求出并集,最后使用count方法统计可达节点数。


有两种具体的实现方式:DP记忆性搜索(递归)
首先讲DP:使用DP需要按照一个合理的计算顺序,由于是有向图,并且靠前的节点计算时需要依赖靠后的节点,所以我们可以想到使用拓扑排序获取一个拓扑序列,然后使用其逆序来作为DP计算的序列。代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<bitset>
#include<stack>

using namespace std;


const int MAX = 30005;
int N, M;
//邻接表,节点编号从1开始
vector<int> adj[MAX];
//入度,出度
int ind[MAX] = { 0 };
int outd[MAX] = { 0 };
//用于dp的数组,储存从该节点出发可达的节点数(包括自身)
int ans[MAX] = { 0 };
//用于dp,储存每个节点对应的bitset
bitset<MAX> bs[MAX];
//标记节点的ans值是否被计算过
bool iscal[MAX] = { false };



void input() {
	cin >> N >> M;
	for (int i = 0; i < M; i++) {
		int u, v;
		cin >> u >> v;
		adj[u].push_back(v);
		outd[u]++;
		ind[v]++;
	}
}



//使用递归求解,id为当前节点
void solve(int id) {
	bs[id].set(id);
	for (int i = 0; i < adj[id].size(); i++) {
		int u = adj[id][i];
		if(!iscal[u])
			solve(u);
		bs[id] = bs[id] | bs[u];
	}

	ans[id] = bs[id].count();
	iscal[id] = true;
}



int main(void) {
	input();

	//从入度为0的节点开始处理
	for (int i = 1; i < N; i++) {
		if(ind[i]==0)	solve(i);
	}
	
	for (int i = 1; i <= N; i++)
		cout << ans[i] << endl;
}

然后讲记忆性搜索。记忆性搜索主要是使用递归来计算,但是如果直接使用递归的话,会因为超时挂60%的测试样例。分析递归代码可知,对于入度为0的节点来说,可能在搜索的过程中相邻节点的状态集早已经计算过,但是仍然又机械的重新算了一遍,因为重复计算太多,所以会超时。作为改进,我们使用一个数组储存节点的状态集是否被计算过,然后在递归中根据这个信息就可以避免大量的重复计算。代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<bitset>
#include<stack>

using namespace std;


const int MAX = 30005;
int N, M;
//邻接表,节点编号从1开始
vector<int> adj[MAX];
//入度,出度
int ind[MAX] = { 0 };
int outd[MAX] = { 0 };
//用于dp的数组,储存从该节点出发可达的节点数(包括自身)
int ans[MAX] = { 0 };
//用于dp,储存每个节点对应的bitset
bitset<MAX> bs[MAX];
//标记节点的ans值是否被计算过
bool iscal[MAX] = { false };

void input() {
	cin >> N >> M;
	for (int i = 0; i < M; i++) {
		int u, v;
		cin >> u >> v;
		adj[u].push_back(v);
		outd[u]++;
		ind[v]++;
	}
}

//使用递归求解,id为当前节点
void solve(int id) {
	bs[id].set(id);
	for (int i = 0; i < adj[id].size(); i++) {
		int u = adj[id][i];
		if(!iscal[u])
			solve(u);
		bs[id] = bs[id] | bs[u];
	}

	ans[id] = bs[id].count();
	iscal[id] = true;
}

int main(void) {
	input();

	//从入度为0的节点开始处理
	for (int i = 1; i < N; i++) {
		if(ind[i]==0)	solve(i);
	}
	
	for (int i = 1; i <= N; i++)
		cout << ans[i] << endl;
}
posted @ 2022-05-04 18:01  带带绝缘体  阅读(35)  评论(0)    收藏  举报