【题解】[CQOI2005] 珠宝

题目信息

题目来源:CCF 重庆省选 2005;

在线评测地址:Luogu#5765

运行限制:时间 \(1.00\ \textrm{s}\),空间 \(128\ \textrm{MiB}\)

(笔者没有在网上找到原题面,于是以下采用的是洛谷的精简题面)

题目描述

有一棵 \(n\) 个结点的树,给每个点安排一个正整数编号,使得相邻点具有不同的编号,编号的总和尽量小。

输入格式

第一行一个整数 \(n\)

以下 \(n-1\) 行,每行两个数 \(u,v\),表示 \(u\)\(v\) 间有一条边。

输出格式

仅一行,为最小编号和。

数据规模与约定

  • 对于 \(20\%\) 的数据,\(n\le 10\)
  • 对于 \(40\%\) 的数据,\(n\le 10^3\)
  • 对于 \(100\%\) 的数据,\(1\le n\le 5\times 10^4\)

保证 \(1\le u,v\le n\)

分析

比较显然的树形 DP。(当时学树形 DP 的第一道例题就是这一个)

树上的问题通常要强行定根,然后进行 BFS 确定 DP 顺序,接下来进行 DP。这道题转移时要注意考虑这个节点的权值及其孩子的权值的可能性。

定义 \(f_{i,j}\) 为第 \(i\) 个点的权值为 \(j\) 时的最小值,则可以得到 \(f_{i,j}=v_{i,j} + \sum\limits_{i\rightarrow p} \min_{v=0}^{v\neq j} f_{p,v}\)

接下来的问题是 \(j\) 的上界,这将会直接影响到复杂度。经试验开到 \(4\) 即可。

注意事项

注意 BFS 完后要倒序DP。

Code

#include <cstdio>
#include <cstring>
using namespace std;

constexpr int max_n = 50000, max_v = 4;
struct node // 队列节点
{
	int id, fa;

	node(int _i = 0, int _f = -1) : id(_i), fa(_f) { }
};

int dp[max_n][max_v], hd[max_n], des[max_n<<1], nxt[max_n<<1], e_cnt = 0;
node que[max_n];

void add_edge(int s, int t)
{
	des[e_cnt] = t;
	nxt[e_cnt] = hd[s];
	hd[s] = e_cnt++;
}

int main()
{
	memset(hd, -1, sizeof(hd));

	int n, ta, tb, ql = 0, qr = 0;
	node cur;

	scanf("%d", &n);
	for (int i = 1; i < n; i++) // 输入,建图
	{
		scanf("%d%d", &ta, &tb);

		add_edge(ta - 1, tb - 1);
		add_edge(tb - 1, ta - 1);
	}

	que[qr++] = node();

	while (ql < qr) // BFS
	{
		cur = que[ql++];

		for (int p = hd[cur.id]; p != -1; p = nxt[p])
			if (des[p] != cur.fa)
				que[qr++] = node(des[p], cur.id); // 防止死循环,加上判断
	}

	for (int i = n - 1; i >= 0; i--)
		for (int j = 0; j < max_v; j++) // 第一重遍历
		{
			dp[que[i].id][j] = j;

			for (int p = hd[que[i].id]; p != -1; p = nxt[p])
			{
				ta = max_v * max_n;

				for (int k = 0; k < max_v; k++) // 第二重遍历
					if (j != k)
					{
						if (dp[des[p]][k] < ta) // 更小就更新
							ta = dp[des[p]][k];
					}
				
				dp[que[i].id][j] += ta;
			}
		}
	
	ta = max_v * max_n;
	for (int i = 0; i < max_v; i++) // 统计答案
		if (dp[0][i] < ta)
			ta = dp[0][i];
	
	printf("%d\n", ta); // 输出

	return 0; // 然后就 AC 了、
}

UPD on 2021-02-24,有关 \(j\) 的上限问题:

这道题,是此题很好的一个延伸。注意到此题讨论的就是 \(j\) 的上限 \(k\)

不妨设根最大(为 \(k\)),则必须使子节点覆盖 \(1\)\(k-1\)。同时,\(1\) 至少要有 \(k\) 个,否则就不能保证存在 \(k\) 最大值。

例外是 \(k-1\),可以只有一个,因为根为 \(k-1\) 会导致子树根为 \(k\),效果相同。

这样的话,定义 \(a_i\) 为最大值为 \(i\) 所需要的最小节点数,\(b_i=\sum\limits_{j=1}^{i-1} (i-j+1)b_j+1\),则:

\[ a_i=\begin{cases} 1&i=1\\ 2&i=2\\ 2\left(\sum\limits_{j=1}^{i-2} (i-j+1)b_j + b_{i-1} + 1\right)&\text{Otherwise} \end{cases} \]

注意到就是这个序列(我才不会告诉你我是看这个序列做出来的)。所以就可以 AC。

同时,回到原题,注意到 \(n\le 50000\),所以理论上 \(j\) 要取 \(11\) 才能保证。但这道题数据水啊,所以就这么过去了(迫真)。

posted @ 2020-08-28 16:57  5ab  阅读(161)  评论(0)    收藏  举报