acwing2716

看到结果可能取很大的值,就可以猜到要用DP。关键在于使用dp的话要按照什么顺序。题目使用的是有向图且食物链有先后顺序,所以联想到拓扑排序,使用拓扑排序得到拓扑序列,从而进行dp。dp[i]表示当前以i为结尾的食物链的条数(此时假设i没有出边)。假设节点i的后继是节点j,那么处理i的时候要对节点的出度减1,并且,更新dp[j]+=dp[i];对于入度为0且出度不为0的节点,初始化其dp值为1。拓扑排序完毕后,即可得到结果。


代码如下:

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

using namespace std;

const int INF = 0x3f3f3f3f;
const int MAX = 100005;
//点数,边数
int n, m;
//邻接表,节点编号从1开始
vector<int> adj[MAX];
//dp数组,储存当前以该节点为末尾的食物链的个数
int dp[MAX] = { 0 };
//节点的入度、出度
int ind[MAX] = { 0 };
int outd[MAX] = { 0 };
//判断节点是否进入过队列
bool inq[MAX] = { false };
//总数
int totalnum = 0;



//读输入,构造邻接表和ind,outd数组
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]++;
	}
}

//通过拓扑排序的顺序,进行DP
void toposort() {
	//队列,储存节点编号
	queue<int> q;

	//将入度为0且出度不为0的节点的dp值设为1,并push入队列
	for (int i = 0; i < n; i++) {
		if (ind[i] == 0 && outd[i] != 0) {
			dp[i] = 1;
			q.push(i);
		}			
	}

	//循环,找拓扑序列
	while (!q.empty()) {
		int cid=q.front();
		q.pop();
		//删除出边
		for (int i = 0; i < adj[cid].size(); i++) {
			int u = adj[cid][i];
			ind[u]--;
			//修改u的dp值
			dp[u] += dp[cid];
			//将新增的入度为0的点加入队列,并更新最终结果
			if (ind[u] == 0) {
				q.push(u);
				if (outd[u] == 0)
					totalnum += dp[u];
			}
			
		}
	}
} 

int main(void) {
	input();
	toposort();
	cout << totalnum << endl;
}
posted @ 2022-05-04 16:13  带带绝缘体  阅读(27)  评论(0)    收藏  举报