一个dfs序的题

一棵树,每个节点上有di个商品,每个商品费用为ci,价值为wi,然后某个人在这棵树上买东西,要求买东西的节点是一个联通块。

输入:

输入第一行一个正整数T,表示测试数据组数。
对于每组数据,
第一行两个正整数n;m;
第二行n个非负整数w1,w2...wn;
第三行n个正整数c1,c2...cn;
第四行n个正整数d1,d2...dn;
接下来n-1行每行两个正整数u;v表示u和v之间有一条道路

输出:每组数据一个数表示答案。

考虑一个暴力,首先枚举每个点必须选,然后以该节点为根,对整棵树做一遍dfs求出dfs序,然后倒着做,记f[i][j]表示点i的答案,首先如果取点i,那么就由i的子树转移,转移之后和不取i(当然也不取i的子树)取max,那么i的子树的答案就记在i+1节点上,最后f[1][j(1<=j<=m)]中的最大值取答案,因为这样转移的话我们枚举的根一定是会选的。

一开始我想直接树形dp,但是发现每次转移需要m^2的时间。

然后我们发现不需要对整棵树做dfs序,只需要对子树做就好了,为什么呢,因为强制选根以及选根上面的点必然会在上面的点当根的时候算上,那么根据这个性质点分治就好了。

有一个很重要的细节,就是我做多重背包是用二进制分组做的,所以当强制选点i的时候,我们需要另外开一个数组,把f[i+1][j]赋值给它,然后如果直接跑背包的话可能会出现一个情况,就是不选点i的商品的收益比选点i个更优秀,所以做二进制分组每个组都强制要选,然后相互间再互相转移。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 505;
int first[MAXN], next[MAXN << 1], go[MAXN << 1], t;
int w[MAXN], c[MAXN], n, m, i, j, k, T, x, y;
int f[MAXN][MAXN << 3], top[MAXN], N, size[MAXN], root, big, tot, ans;
int cost[MAXN][20], gain[MAXN][20], len[MAXN], g[20][MAXN << 3];
bool vis[MAXN];
inline int get()
{
    char c;
    while ((c = getchar()) < 48 || c > 57);
    int res = c - 48;
    while ((c = getchar()) >= 48 && c <= 57)
    res = res * 10 + c - 48;
    return res;
}
inline void add(int x, int y)
{
	next[++t] = first[x]; first[x] = t; go[t] = y;
}
inline void getroot(int now, int las, int nn)
{
	int p = 0;
	size[now] = 1;
	for(int i = first[now]; i; i = next[i])
		if (go[i] != las && !vis[go[i]]) getroot(go[i], now, nn), size[now] += size[go[i]], p = max(p, size[go[i]]);
	p = max(p, nn - size[now]);
	if (p < big) big = p, root = now;
}
inline void dfs(int now, int las)
{
	top[++t] = now;
	size[now] = 1;
	for(int i = first[now]; i; i = next[i])
		if (go[i] != las && !vis[go[i]]) dfs(go[i], now), size[now] += size[go[i]];
}
inline void solve(int now)
{
	vis[now] = 1;
	t = 0;
	dfs(now, 0);
	memset(f[t + 1], 0, sizeof(f[t + 1]));
	for(int i = t; i >= 1; i --)
	{
		for(j = 0; j <= m; j ++)
			g[0][j] = f[i + 1][j];
		for(k = 1; k <= len[top[i]]; k ++)
		{
			if (k > 1)
			{
				for(j = 0; j <= m; j ++)
					g[k][j] = g[k - 1][j];
			}
			else {
				for(j = 0; j <= m; j ++)
					g[k][j] = 0;
			}
			for(j = cost[top[i]][k]; j <= m; j ++)
				g[k][j] = max(g[k][j], g[0][j - cost[top[i]][k]] + gain[top[i]][k]);
			for(j = cost[top[i]][k]; j <= m; j ++)
				g[k][j] = max(g[k][j], g[k - 1][j - cost[top[i]][k]] + gain[top[i]][k]); 
		}
		for(j = 0; j <= m; j ++)
			f[i][j] = max(g[len[top[i]]][j], f[i + size[top[i]]][j]);
	}
	for(j = 0; j <= m; j ++)
		ans = max(ans, f[1][j]);
	for(int i = first[now]; i; i = next[i])
		if (!vis[go[i]])
		{
			big = n;
			getroot(go[i], now, size[go[i]]);
			solve(root);
		}
}
int main()
{
	cin >> T;
	while (T --)
	{
		cin >> n >> m;
		t = 0;
		memset(first, 0, sizeof(first));
		memset(vis, 0, sizeof(vis));
		ans = 0;
		for(i = 1; i <= n; i ++)
			w[i] = get();
		for(i = 1; i <= n; i ++)
			c[i] = get();
		for(i = 1; i <= n; i ++)
		{
			x = get();
			int num = 1;
			len[i] = 0;
			for(;;)
			{
				if (x - num < 0) break;
				x -= num;
				len[i] ++;
				cost[i][len[i]] = c[i] * num;
				gain[i][len[i]] = w[i] * num;
				num <<= 1;
			}
			if (x) cost[i][++len[i]] = c[i] * x, gain[i][len[i]] = w[i] * x;
		}
		for(i = 1; i < n; i ++)
		{
			x = get(); y = get();
			add(x, y);
			add(y, x);
		}
		big = n;
		getroot(1, 0, n);
		solve(root);
		cout << ans << endl;
	}
}


资源下载链接为: https://pan.quark.cn/s/1bfadf00ae14 “STC单片机电压测量”是一个以STC系列单片机为基础的电压检测应用案例,它涵盖了硬件电路设计、软件编程以及数据处理等核心知识点。STC单片机凭借其低功耗、高性价比和丰富的I/O接口,在电子工程领域得到了广泛应用。 STC是Specialized Technology Corporation的缩写,该公司的单片机基于8051内核,具备内部振荡器、高速运算能力、ISP(在系统编程)和IAP(在应用编程)功能,非常适合用于各种嵌入式控制系统。 在源代码方面,“浅雪”风格的代码通常简洁易懂,非常适合初学者学习。其中,“main.c”文件是程的入口,包含了电压测量的核心逻辑;“STARTUP.A51”是启动代码,负责初始化单片机的硬件环境;“电压测量_uvopt.bak”和“电压测量_uvproj.bak”可能是Keil编译器的配置文件备份,用于设置编译选项和项目配置。 对于3S锂电池电压测量,3S锂电池由三节锂离子电池串联而成,标称电压为11.1V。测量时需要考虑电池的串联特性,通过分压电路将高电压转换为单片机可接受的范围,并实时监控,防止过充或过放,以确保电池的安全和寿命。 在电压测量电路设计中,“电压测量.lnp”文件可能包含电路布局信息,而“.hex”文件是编译后的机器码,用于烧录到单片机中。电路中通常会使用ADC(模拟数字转换器)将模拟电压信号转换为数字信号供单片机处理。 在软件编程方面,“StringData.h”文件可能包含程中使用的字符串常量和数据结构定义。处理电压数据时,可能涉及浮点数运算,需要了解STC单片机对浮点数的支持情况,以及如何高效地存储和显示电压值。 用户界面方面,“电压测量.uvgui.kidd”可能是用户界面的配置文件,用于显示测量结果。在嵌入式系统中,用
<think>嗯,用户想要用链式前向星和vector分别实现树的DFS生成,并且记录入栈和出栈时间。首先我得回忆一下这两种数据结构在存储树结构时的不同实现方式。 链式前向星的话,通常是用数组来模拟链表,每个节点都有一个头指针,指向该节点的第一条边,然后每条边保存下一个边的索引。DFS遍历的时候,需要从头指针开始,沿着next指针遍历所有邻接节点。不过要注意,无向图的话,每条边会被存储两次,所以遍历的时候需要避免回溯到父节点,否则会死循环。所以在DFS函数里,要记录当前节点的父节点,然后遍历邻接节点时跳过父节点。 然后是vector实现,这个相对简单,每个节点对应一个vector,里面存储所有连接的节点。同样,在DFS的时候需要防止回溯,所以每次访问子节点时,要检查是否已经访问过,或者是否是父节点。 接下来是DFS的问DFS一般包括入栈时间(进入节点的时间戳)和出栈时间(离开节点的时间戳)。通常,入栈时间是在第一次访问该节点时记录,出栈时间是在回溯时,所有子节点处理完毕后记录。这两个时间戳可以用来判断节点之间的祖先关系等。 对于代码结构,链式前向星需要先构建图的存储结构,包括边数组和head数组。然后进行DFS,每次访问节点时记录入栈时间,递归访问所有子节点(排除父节点),最后在返回前记录出栈时间。同样,vector的实现也是类似,只不过邻接表的访问方式不同。 需要注意的是,时间戳的递增顺。每次进入节点时增加时间戳,离开时再增加。例如,入栈时间是进入时记录,然后处理子节点,处理完所有子节点后,离开时记录出栈时间。 然后要处理输入输出,根据不同的存储结构,初始化方式也不同。链式前向星需要逐个添加边,而vector则是push_back到对应的位置。 可能遇到的问包括:时间戳的正确递增,避免重复访问父节点,以及如何处理大规模数据时的效率问。例如,链式前向星在遍历时需要用循环遍历每个邻接边,而vector则可以直接用范围for循环。 另外,用户提供的引用中有提到链式前向星的遍历代码,以及vector存储树结构的方式,这些可以作为参考。例如,引用3中提到了用vector数组存储树,每个节点的连接节点都存在对应的vector里。而引用2中链式前向星的遍历方式,使用head数组和循环。 最后,要确保代码示例正确,包括变量的初始化,DFS的递归实现,以及时间戳的处理。同时,测试用例的输入输出是否符合预期,比如树的重心问中的输入样例,可以用来验证代码的正确性。</think>以下分别给出链式前向星和vector实现DFS的C++代码,包含入栈时间和出栈时间标记: ### 一、链式前向星实现 ```cpp #include <iostream> #include <cstring> using namespace std; const int N = 1e5 + 10; int head[N], cnt = 0; int in[N], out[N], timestamp = 0; struct Edge { int to; int next; } edges[N*2]; void addEdge(int u, int v) { edges[++cnt].to = v; edges[cnt].next = head[u]; head[u] = cnt; } void dfs(int u, int father) { in[u] = ++timestamp; // 记录入栈时间 for (int i = head[u]; i; i = edges[i].next) { int v = edges[i].to; if (v != father) { // 防止回溯 dfs(v, u); } } out[u] = ++timestamp; // 记录出栈时间 } int main() { memset(head, 0, sizeof(head)); int n; cin >> n; // 建图 for (int i = 1; i < n; i++) { int a, b; cin >> a >> b; addEdge(a, b); addEdge(b, a); } dfs(1, -1); // 假设根节点为1 // 输出DFS for (int i = 1; i <= n; i++) { cout << "Node " << i << ": in=" << in[i] << ", out=" << out[i] << endl; } return 0; } ``` ### 二、vector实现 ```cpp #include <iostream> #include <vector> using namespace std; const int N = 1e5 + 10; vector<int> edges[N]; int in[N], out[N], timestamp = 0; void dfs(int u, int father) { in[u] = ++timestamp; // 记录入栈时间 for (int v : edges[u]) { if (v != father) { // 防止回溯 dfs(v, u); } } out[u] = ++timestamp; // 记录出栈时间 } int main() { int n; cin >> n; // 建图 for (int i = 1; i < n; i++) { int a, b; cin >> a >> b; edges[a].push_back(b); edges[b].push_back(a); } dfs(1, -1); // 假设根节点为1 // 输出DFS for (int i = 1; i <= n; i++) { cout << "Node " << i << ": in=" << in[i] << ", out=" << out[i] << endl; } return 0; } ``` ### 代码要点说明: 1. **时间戳生成**:每个节点$u$的$in[u]$在首次访问时记录,$out[u]$在所有子节点访问完成后记录 2. **防回溯机制**:通过记录父节点参数避免重复访问(关键差异点) 3. **存储差异**: - 链式前向星通过`head[]`数组索引边集合 - vector直接存储邻接表 4. **时间复杂度**:两者均为$O(n)$,但vector实现常数更小[^3] 输入样例测试(引用[4]的案例): ``` 9 1 2 1 7 1 4 2 8 2 5 4 3 3 9 4 6 ``` 输出结果将展示每个节点的访问顺和回溯时间,可用于后续子树规模计算等操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值