[SDOI2010] 城市规划 题解

前言

题目链接:洛谷

题意简述

树套环上求至少间隔两个位置的最大独立集。

(树套环,即树上每个结点都是一个结点或环)

题目分析

将题目拆解成树上 DP 和环上 DP 即可。用 tarjan 缩点就行。

树上 DP

先来看看树上 DP。

显然每个点有三个状态:不选中且周围没选中、选中、不选中但在选中的点的旁边。记成 \(f[yzh][0/1/2]\)。设 \(xym\)\(yzh\) 的一个孩子。

先来看看 \(f[yzh][0]\)。由于她不选中,且不在一个选中的旁边,可以从孩子的 \(0\)\(2\) 状态转移而来。

\[f[yzh][0] = \sum \max \Big \lbrace f[xym][0], f[xym][2] \Big \rbrace \]

\(f[yzh][1]\)。由于她选中,那么孩子在之前必须没被选中,且不在选中的点的旁边,即只能从孩子的 \(0\) 状态转移而来。注意加上她的价值。

\[f[yzh][1] = \operatorname{val}(yzh) + \sum f[xym][0] \]

\(f[yzh][2]\)。她能且只能从一个孩子的 \(1\) 状态转移而来,其他儿子要么是 \(0\) 状态,要么是 \(2\) 状态。取最大值。

\[f[yzh][2] = f[xym'][1] + \sum _ {xym \neq xym'} \max \Big \lbrace f[xym][0], f[xym][2] \Big \rbrace \]

这样,得到 \(20 \%\) 树的部分分。

环上 DP

先把环“拉下来”,即把环按照一定顺序存下来。类似于树,记 \(g[i][0/1/2]\) 表示考虑到了环上前 \(i\) 个点,第 \(i\) 个点状态下最大价值,再记 \(f[i][0/1/2]\) 表示从第 \(i\) 个点,走到子树里,树形 DP 的结果。先不考虑其他的,来看看转移方程。

\(g[yzh][0]\)。在树里,她不能被选中,即 \(f[yzh][0]\);在环里,前一个位置不能是 \(1\) 状态。

\[g[yzh][0] = f[yzh][0] + \max \lbrace g[yzh - 1][0], g[yzh - 1][2] \rbrace \]

\(g[yzh][1]\)。不妨把 \(yzh\) 的价值计算到树里。环里前一个位置之前不能选。那么就是 \(f[yzh][1]\)\(g[yzh - 1][0]\) 的和。

\[g[yzh][1] = f[yzh][1] + g[yzh - 1][0] \]

\(g[yzh][2]\)。这里需要分类讨论,这个 \(2\) 状态可能是应为环里前一个位置是 \(1\) 状态,或者树里达到了 \(2\) 状态。取最大值即可。

\[g[yzh][2] = \max \Big \lbrace g[yzh - 1][1] + f[yzh][0], \max \lbrace g[yzh - 1][0], g[yzh - 1][2] \rbrace + f[yzh][2] \Big \rbrace \]

以上就是转移方程。重点在环的特殊性。所以套路化地想到,把左侧强制设为某一个状态,那么右侧只有部分状态是有效的。为了之后树形 DP,我们不妨把这个环的“上顶点”移到我们“拉下来”的数组的右侧。

  1. 若左侧是 \(0\) 状态。
    那么右侧三个状态都合法。
  2. 若左侧是 \(1\) 状态。
    那么右侧只能是环上 DP 出来的 \(0\) 状态,并且在反映到树形 DP 上时是 \(2\) 状态。
  3. 若左侧是 \(2\) 状态。
    那么右侧 \(0\) 状态或 \(2\) 状态都是合法的。

这样看起来没有问题了。但是,还是会错,给出我对拍出来的一组数据:

10 11
5 1 1 1 3 5 7 2 8 9
1 2
2 3
3 4
4 5
5 1
6 7
7 8
8 9
9 10
10 6
1 6

答案显然是 \(13\),可是我们输出了 \(14\)(好吧,可能有那么一点不显然)

为什么呢?问题就出在了我们对左侧 \(0\) 状态的考虑。这个位置的右边可能后来被选中了,而在树形 DP 上传答案的时候,右侧也是选中的,这样两者中间只间隔了 \(1\) 个,冲突了。

如何解决呢?再强制以下就好了:一遍强制左侧右边那个位置不能选中,就可以放心转移了。

代码

代码实现很清晰。略去了快读。注意用 vector 可能会含泪 MLE \(90\) 分,用链式前向星即可。可能有之前卡空间的遗物?

#include <vector>
#include <array>
#include <cstdio>
#include <stack>
using namespace std;

using node = array<int, 3>;

inline int max(int a, int b, int c) {
	return max(max(a, b), c);
}

struct Graph{
	struct node{
		int to, nxt;
	} edge[2000010 << 1];
	int eid, head[1000010];
	inline void add(int u, int v){
		edge[++eid] = {v, head[u]};
		head[u] = eid;
	}
	inline node & operator [] (const int x){
		return edge[x];
	}
} xym;

int n, m, val[1000010], ans;
vector<int> yzh[1000010];

int dfn[1000010], low[1000010], timer;
stack<int> st;
int sccno[1000010], scc_cnt;
void tarjan(int now, int fr){
	dfn[now] = low[now] = ++timer, st.push(now);
	for (int i = xym.head[now], to; to = xym[i].to, i; i = xym[i].nxt) {
		if (!dfn[to]) tarjan(to, now), low[now] = min(low[now], low[to]);
		else if (to != fr) low[now] = min(low[now], dfn[to]);
	}
	if (low[now] >= dfn[now]){
		++scc_cnt;
		while (true) {
			int tp = st.top(); st.pop();
			yzh[scc_cnt].push_back(tp), sccno[tp] = scc_cnt;
			if (tp == now) break;
		}
	}
}

void move_to_end(vector<int> &vec, int x) {
	vector<int> res;
	int len = vec.size(), pos = 0;
	for (int i = 0; i < len; ++i) {
		if (vec[i] == x) {
			pos = i;
			break;
		}
	}
	for (int i = pos + 1; i < len; ++i)
		res.push_back(vec[i]);
	for (int i = 0; i < pos; ++i)
		res.push_back(vec[i]);
	res.push_back(x);
	vec = move(res);
}

bool vis[1000010];
node dfs(int now, int top, int fa) {
	vis[now] = true;
	move_to_end(yzh[now], top);  // 把 top 放在最后
	int tot = yzh[now].size();
	vector<node> f(tot, {0, 0, 0}), dp(tot, {0, 0, 0});
	for (int i = 0; i < tot; ++i) {
		int son = yzh[now][i];
		f[i][0] = 0, f[i][1] = val[son], f[i][2] = 0;
		int mx = -0x3f3f3f3f;
		for (int j = xym.head[son], to; to = xym[j].to, j; j = xym[j].nxt) {
			int bl = sccno[to];
			if (bl == fa || bl == now) continue;
			node yzh = dfs(bl, to, now);
			f[i][0] += max(yzh[0], yzh[2]);
			f[i][1] += yzh[0];
			f[i][2] += max(yzh[0], yzh[2]);
			mx = max(mx, yzh[1] - max(yzh[0], yzh[2]));
		}
		f[i][2] += mx;
	}
	node res = {-0x3f3f3f3f, -0x3f3f3f3f, -0x3f3f3f3f};
	
	yzh[now].clear();
	yzh[now].shrink_to_fit();
	
	if (tot == 1) {
		res[0] = f[0][0];
		res[1] = f[0][1];
		res[2] = f[0][2];
		return res;
	}
	
	// 左边是空的 并且可能被 1 占用
	dp[0][0] = f[0][0], dp[0][1] = -0x3f3f3f3f, dp[0][2] = -0x3f3f3f3f;
	for (int i = 1; i < tot; ++i) {
		dp[i][0] = f[i][0] + max(dp[i - 1][0], dp[i - 1][2]);
		dp[i][1] = f[i][1] + dp[i - 1][0];
		dp[i][2] = max(dp[i - 1][1] + f[i][0], max(dp[i - 1][0], dp[i - 1][2]) + f[i][2]);
	}
	res[0] = max(res[0], dp[tot - 1][0]);
	res[2] = max(res[2], dp[tot - 1][2]);
	
	// 左边是空的 并且不能被 1 占用
	dp[0][0] = f[0][0], dp[0][1] = -0x3f3f3f3f, dp[0][2] = -0x3f3f3f3f;
	for (int i = 1; i < tot; ++i) {
		dp[i][0] = f[i][0] + max(dp[i - 1][0], dp[i - 1][2]);
		if (i != 1) dp[i][1] = f[i][1] + dp[i - 1][0];
		else dp[i][1] = -0x3f3f3f3f;
		dp[i][2] = max(dp[i - 1][1] + f[i][0], max(dp[i - 1][0], dp[i - 1][2]) + f[i][2]);
	}
	res[0] = max(res[0], dp[tot - 1][0]);
	res[1] = max(res[0], dp[tot - 1][1]);
	res[2] = max(res[2], dp[tot - 1][2]);
	
	// 左边在真的旁边
	dp[0][0] = -0x3f3f3f3f, dp[0][1] = -0x3f3f3f3f, dp[0][2] = f[0][2];
	for (int i = 1; i < tot; ++i) {
		dp[i][0] = f[i][0] + max(dp[i - 1][0], dp[i - 1][2]);
		dp[i][1] = f[i][1] + dp[i - 1][0];
		dp[i][2] = max(dp[i - 1][1] + f[i][0], max(dp[i - 1][0], dp[i - 1][2]) + f[i][2]);
	}
	res[0] = max(res[0], dp[tot - 1][0]);
	res[2] = max(res[2], dp[tot - 1][2]);
	
	// 左边是真的
	dp[0][0] = -0x3f3f3f3f, dp[0][1] = f[0][1], dp[0][2] = -0x3f3f3f3f;
	for (int i = 1; i < tot; ++i) {
		dp[i][0] = f[i][0] + max(dp[i - 1][0], dp[i - 1][2]);
		dp[i][1] = f[i][1] + dp[i - 1][0];
		dp[i][2] = max(dp[i - 1][1] + f[i][0], max(dp[i - 1][0], dp[i - 1][2]) + f[i][2]);
	}
	res[2] = max(res[2], dp[tot - 1][0]);
	
	f.clear(), f.shrink_to_fit();
	dp.clear(), dp.shrink_to_fit();
	
	return res;
}

signed main() {
	read(n), read(m);
	for (int i = 1; i <= n; ++i) read(val[i]);
	for (int i = 1, u, v; i <= m; ++i) {
		read(u), read(v);
		xym.add(u, v), xym.add(v, u);
	}
	for (int i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i, 0);
	for (int i = 1; i <= n; ++i)
		if (!vis[sccno[i]]) {
			node res = dfs(sccno[i], i, 0);
			ans += max(res[0], res[1], res[2]);
		}
	printf("%d", ans);
	return 0;
}

后记 & 反思

没有什么是分类讨论解决不了的。如果有,那是你没有讨论细致。

posted @ 2024-07-13 14:53  XuYueming  阅读(4)  评论(0编辑  收藏  举报