一个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;
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值