洛谷 P8595 「KDOI-02」一个网的路 蓝 题解

定义

树形 \(DP\),这个挺明显的,我认为 \(DP\) 让读者理解的最重要的一步是:定义。

所以我先详细说明 \(f\) 数组的定义,至于为什么这么定义后面再讲。

\(f_{u, type}\),其中 \(type=0,1,2\)

\(f_{u,0}\) 表示:在原森林中,以 \(u\) 为根节点的子树变成链的最小代价,其中 \(u\) 必须被炸掉。

\(f_{u,1}\) 表示:在原森林中,以 \(u\) 为根节点的子树变成链的最小代价,其中 \(u\) 必须不被炸掉,且 \(u\) 最多有一个儿子。

\(f_{u,2}\) 表示:在原森林中,以 \(u\) 为根节点的子树变成链的最小代价,其中 \(u\) 必须不被炸掉,且 \(u\) 最多有两个儿子。

为什么这样定义

首先,一个节点炸或者不炸,是会影响它向父亲节点转移的,所以必须分类:炸掉、不炸掉。

而一个点如果不被炸掉,那么它应该有几个儿子呢?可以一个都没有,所有儿子都被炸掉;可以有一个或者两个,对应链的情况。如果有超过三个儿子,那么不可能形成链。

所以就先这么定义,看看能不能转移。

状态转移方程

类型 0

考虑 \(f_{u,0}\) 的状态转移方程。

首先,\(u\) 点必须被炸掉,所以代价加一

其次,炸了 \(u\) 之后,\(u\) 有几个儿子就需要再连几条边,因为最后要形成链,所以代价加上 \(u\) 的儿子数量

最后,你还需要把儿子节点的信息传递上来,由于炸了 \(u\) 之后,\(u\) 和它的所有儿子都不联通,那么 \(u\) 的儿子的类型是几并不重要,那么代价还需要加上 \(\sum\limits_{v\in son_u } \min(f_{v,0}-1, f_{v,1}, f_{v,2})\)

为什么有一项是 \(f_{v,0}-1\)?为啥要减一?因为在之前遍历到 \(v\) 的时候,\(u,v\) 之间的边被炸了一次;现在炸 \(u\) 的时候,\(u,v\) 之间的边又被炸了一次,重复了。

我们令 \(d_u\) 表示 \(u\) 的儿子的数量,那么 \(f_{u,0} = \sum\limits_{v\in son_u } \min(f_{v,0}-1, f_{v,1}, f_{v,2}) + d_u + 1\)

类型 1

首先,\(u\) 不能被炸,如果让 \(u\) 只剩下 \(1\) 个儿子,我们可以来一个简单的容斥。

先把 \(u\) 的所有儿子都炸掉,然后选择一个儿子恢复,当然有可能不选,因为是最多一个儿子

考虑第一步,代价很简单,为 \(\sum\limits_{v\in son_u} f_{v,0}\)

对于第二步,我们要挑选一个 \(v\),先将代价减去 \(f_{v,0}\) ,表示我不炸这个 \(v\) 了,让它留着做 \(u\) 的儿子;再将代价加上 \(f_{v,1}\),表示将信息转移到父亲,至于为什么和 \(f_{v,2}\) 没有关系,后面再说。

所以儿子都炸完之后,我们要找一个最大的 \(f_{v,0} - f_{v,1}\),让原来的代价减去它,即为最终代价,如果所有的 \(f_{v,0} - f_{v,1}\) 都小于零,那就不要儿子了,因为要上儿子只能使代价变大。

为什么和 \(f_{v,2}\) 没有关系?因为不可能由 \(f_{v,2}\) 转移过来,因为一个点有一个儿子,而这个儿子又有两个儿子,那么肯定不可能组成一条链,如果还不懂的话,我画个图:

如果你可以什么操作都不做把它画成一条链我就当场认爹

所以状态转移方程为:\(f_{u,1} = \sum\limits_{v\in son_u} f_{v,0} - \max(0, \max_{v\in son_u}(f_{v,0} - f_{v,1}))\)

类型 2

这个简单,在类型 1 的基础上再减去一个次大的大于零的 \(f_{v,0} - f_{v,1}\) 即可。

\(f_{u,2} = f_{u,1} - \max(0, SEC_{v \in son_u}(f_{v,0} - f_{v,1}))\) (这里的 \(SEC\) 指第二大的数)

整理

\(f_{u,0} = \sum\limits_{v\in son_u } \min(f_{v,0}-1, f_{v,1}, f_{v,2}) + d_u + 1\)

\(f_{u,1} = \sum\limits_{v\in son_u} f_{v,0} - \max(0, \max_{v\in son_u}(f_{v,0} - f_{v,1}))\)

\(f_{u,2} = f_{u,1} - \max(0, SEC_{v \in son_u}(f_{v,0} - f_{v,1}))\)

求答案

首先对于每一棵树,我们的答案很显然:为 \(\min(f_{u,0}, f_{u,1}, f_{u,2})\),但这是一个森林,所以我们还需要加上一些边让整森林变成一颗大树,加边数量为:\(n-1-m\),即总共需要 \(n-1\) 条边,题目给了 \(m\) 条边,那么还需要再加上 \(n-1-m\) 条边

所以 \(Ans = \sum\limits_{u\in root} \min(f_{u,0}, f_{u,1}, f_{u,2}) + n - 1 - m\)

code

#include <iostream>
#include <cstring>

using namespace std;

const int N = 2e6 + 10, M = 2 * N;

int n, m, ans;
int h[N], e[M], ne[M], idx;
int d[N], f[N][3];
bool vis[N];

int read()
{
	int s = 0, w = 1;
	char c = getchar();
	while (c < '0' || c > '9')
	{
		if (c == '-') w = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		s = s * 10 + c - '0';
		c = getchar();
	}
	return s * w;
}

void add(int a, int b)
{
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx ++ ;
	return;
}

void dfs(int u)
{
	vis[u] = true;
	int fir = 0, sec = 0;
	for (int i = h[u]; ~i; i = ne[i])
	{
		int ver = e[i];
		if (vis[ver]) continue;
		dfs(ver); 
		int delta = f[ver][0] - f[ver][1];
		if (delta > fir) sec = fir, fir = delta;
		else if (delta > sec) sec = delta;
		f[u][0] += min(f[ver][0] - 1, min(f[ver][1], f[ver][2]));
		f[u][1] += f[ver][0];
	}
	f[u][0] += d[u] + 1;
	f[u][1] -= fir;
	f[u][2] = f[u][1] - sec;
	return;
}

int main()
{
	n = read(), m = read();
	ans = (n - 1) - m;
	memset(h, -1, sizeof h);
	for (int i = 1; i <= m; i ++ )
	{
		int a, b;
		a = read(), b = read();
		add(a, b);
		add(b, a);
		d[a] ++ ;
		d[b] ++ ;
	}
	for (int i = 1; i <= n; i ++ )
	{
		if (!vis[i]) 
		{
			dfs(i);
			ans += min(f[i][0], min(f[i][1], f[i][2]));
		}
	}
	cout << ans << endl;
	return 0;
}

/*
   _____
  /     \
 /  'v'  \
/  __O__  \
\____|____/
*/

完结撒花!

posted @ 2022-10-27 12:00  LittleMoMol  阅读(105)  评论(0编辑  收藏  举报
*/