洛谷_P4017 最大食物链计数 (尚贤)关于dp和记忆化搜索取舍

【题目】:https://www.luogu.com.cn/problem/P4017

耙耙:

昨晚:孩子说这题是洛谷dp,但是dp不会做,用什么数据展示用什么开始。想到何老师说过dp可以用搜索搞掂。

所以考虑,那些问题可以用搜索来代替dp。  考虑他们之间有共通吗?  

今天:孩子记忆化搜索搞掂了这题。不过要用dp不懂,刚看了题解,需要用到拓扑排序,他说也看懂了这个拓扑排序,又用拓扑排序写一次。

结论:

何老师:dp可以用搜索做。

洛谷_P4017 最大食物链计数 (尚贤)关于dp和记忆化搜索取舍_第1张图片

网上意见: 如果好找顺序的话,写循环很好写,如果找不到顺序那就记忆化搜索。

其他意见:能写dp就写dp,别投机取巧;搜索很容易想 dp不容易;dp到最后 就是数学;本来动态规划就是数学分支

洛谷_P4017 最大食物链计数 (尚贤)关于dp和记忆化搜索取舍_第2张图片

 

最下面有两种AC代码,记忆化搜索代码和拓扑排序代码。

 

抄人的 文字讲解

题目分析:

首先 ,要知道这道拓扑排序题目的性质。

食物链中的生物 —— 节点

生物之间的关系 —— 有向边

为了方便描述,我们

将 最左端是不会捕食其他生物的生产者 叫做 最佳生产者

将 最右端是不会被其他生物捕食的消费者 叫做 最佳消费者

数据中不会出现环

那么,“最大食物链”就是左端是 最佳生产者 ,右端是 最佳消费者

思路引导

易得,想要找到一条 最大食物链 ,则起始点入度要为0,终点出度要为0。于是有:

既要记录入度,还要记录出度!

现在的问题就是,如何找到所有的最大食物链的数量

正解

我们拿起笔,在草稿纸上先画一个图做参考。那么我们就拿样例进行举例吧。

(我先将最佳生产者表上蓝色,最佳消费者表上红色)

洛谷_P4017 最大食物链计数 (尚贤)关于dp和记忆化搜索取舍_第3张图片

发现:答案为 到所有最佳消费者路径条数的总和

(这里的路径总和不是 连向它有几条边 ,而是以它结束的 最大食物链 数量的总和)

对于上面的图,55 号点的对应路径数量 取决于:以 到 55 号点的三个点( 22 号、33 号、44 号) 结尾的 最大食物链个数 的总和。

而 以 22 号、33 号、44 号 结尾的 最大食物链个数 取决于:以 到达 22 号、33号、44 号 的点 结尾的 最大食物链个数 的总和。

以此类推,对于 以 任一点 结尾的 最大食物链的 数量,都取决于 蓝色点

各点数量对应关系在下图用绿色边标注

洛谷_P4017 最大食物链计数 (尚贤)关于dp和记忆化搜索取舍_第4张图片

重点:

我们不妨使用拓扑排序,由题意得知 TopoTopo 排序第一轮被删掉的点 一定是 蓝色点(最佳生产者),而 蓝色点 的答案为 11。

当第一轮删点时,将蓝色点可以到的点 的答案 都加上 蓝色点的 答案(即加 11)。

即:拓扑排序 需要删除的点的答案 都累加到 它可以到达的点 上面去

最后累加所有 红色点(最佳消费者) 的答案,输出即可。

以第 i 个点结束的 最大食物链的 数量 = 以 指向第 i 个点的点 结尾的 最大食物链的 数量的和

以下是模拟操作过程:

加载时间较慢,请稍等

第一轮:删除 11 号蓝色点,11 号蓝色点可以到的点(22 号点、33 号点)都加 11

洛谷_P4017 最大食物链计数 (尚贤)关于dp和记忆化搜索取舍_第5张图片

第二轮:删除 22 号点,22 号点可以到的点(33 号点、55 号红色点)都加 11。此时 33 号点答案为 22,55 号点答案为 11

洛谷_P4017 最大食物链计数 (尚贤)关于dp和记忆化搜索取舍_第6张图片

第三轮:删除 33 号点,33 号点可以到的点(44 号点、55 号红色点)都加 22。此时 55 号点答案为 33,44 号点答案为 22

洛谷_P4017 最大食物链计数 (尚贤)关于dp和记忆化搜索取舍_第7张图片

第四轮:最后删除 44 号点,44 号点可以到的点(55 号红色点)加 22,此时 55 号点答案为 55

洛谷_P4017 最大食物链计数 (尚贤)关于dp和记忆化搜索取舍_第8张图片

可见全图只有 55 号一个红色点,那么答案就是 55 号点的答案———— 55 了

洛谷_P4017 最大食物链计数 (尚贤)关于dp和记忆化搜索取舍_第9张图片

那么代码实现就很简单了!

上代码:

#include//万能头,懒人必备 
using namespace std;//标准名称函数库开启 

const int MAXN = 5000 + 10;//定义常量大小 
const long long mod = 80112002;//定义最终答案mod的值 

int n,m;//n个点and边的关系数m
int in[MAXN];//记录每个点的入度 
int out[MAXN];//记录每个点的出度 
vectornei[MAXN];//记录每个点相邻的点有哪些,用动态数组更加节省时间 
queueq;//拓扑排序模板队列 
int ans;//答案 
int num[MAXN];//记录到这个点的路径数量 


inline int read(){//快速读入 
    int f = 1, x = 0;
    char c = getchar();

    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }

    while (c >= '0' && c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }

    return f * x;
} 

int main(){//开始...... 
	n = read(),m = read();//快速读入 
	for(int i = 1;i <= m; i++){//循环m条边 
		int x = read(),y = read();//输入左节点和右节点 
		in[y]++,out[x]++;//右节点入度+1,左节点出度+1 
		nei[x].push_back(y);//建立一条单向边 
	}
	for(int i = 1;i <= n; i++){//初次寻找入度为0的点(最佳生产者)
		if(in[i] == 0){//符合要求 
			num[i] = 1;//令到其的路径数量为1 
			q.push(i);//压入队列 
		}
	}
	while(!q.empty()){//只要还有入度为0的点 
		int tot = q.front();//取出首点 
		q.pop();//弹出 
		for(int i = 0;i < nei[tot].size(); i++){//枚举这个点相邻的所有点
			int next = nei[tot][i]; //取出目前枚举到的点 
			in[next]--;//将这个点的入度-1(因为目前要删除第tot个点) 
			num[next] = (num[next] + num[tot]) % mod;//更新到next点的路径数量 
			if(in[next] == 0)q.push(nei[tot][i]);//如果这个点的入度为0了,那么压入队列 
		}
	}
	for(int i = 1;i <= n; i++){//寻找出度为0的点(最佳消费者) 
		if(out[i] == 0){//符合要求 
			ans = (ans + num[i]) % mod;//累加答案 
		}
	}
	cout<

孩子的AC代码 记忆化搜索代码: 

#include 
#include 
#define ll long long
using namespace std;
vector a[5005];
bool pd[5005];
ll dp[5005];

ll dfs(const int &t);

int main() { 
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int x, y;
		scanf("%d%d", &x, &y);
		a[y].push_back(x);
		pd[x] = true;
	}
	ll ans = 0;
	for (int i = 1; i <= n; ++i) {
		if (a[i].size() == 0) {
			dp[i] = 1ll;
		}
	}
	for (int i = 1; i <= n; ++i) {
		if (!pd[i]) {
			ans += dfs(i);
			ans %= 80112002;
		}
	}
	printf("%lld\n", ans);
	return 0;
}

ll dfs(const int &t) {
	if (dp[t]) return dp[t] % 80112002;
	else dp[t] = 0;
	for (int i = 0; i < a[t].size(); ++i) {
		dp[t] += dfs(a[t][i]);
		dp[t] %= 80112002;
	}
	return dp[t];
}

孩子的拓扑排序代码:

#include 
#include 
#include 
#define mod 80112002
using namespace std;
vector map[5005];
queue q;
int in[5005], out[5005], number[5005];
int main() { 
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int A, B;
		scanf("%d%d", &A, &B);
		++in[B];
		++out[A];
		map[A].push_back(B);
	}
	for (int i = 1; i <= n; ++i)
		if (!in[i]) {
			q.push(i);
			number[i] = 1;
		}
	while (!q.empty()) {
		int now = q.front();
		q.pop();
		for (int i = 0; i < map[now].size(); ++i) {
			int cnt = map[now][i];
			--in[cnt];
			number[cnt] = (number[cnt] + number[now]) % mod;
			if (in[cnt] == 0) q.push(cnt);
		}
	}
	int ans = 0;
	for (int i = 1; i <= n; ++i)
		if (!out[i])
			ans = (ans + number[i]) % mod;
	printf("%d\n", ans);
	return 0;
}

 

你可能感兴趣的:(洛谷_P4017 最大食物链计数 (尚贤)关于dp和记忆化搜索取舍)