题解:P2607 [ZJOI2008] 骑士

原本打算做出来之后在洛谷上面交的,然后一看已经满了,所以就在别的地方发表吧。

打状态转移方程实在太辛苦了!!! \tiny \text{打状态转移方程实在太辛苦了!!!} 打状态转移方程实在太辛苦了!!!

题意:

给定一个无向基环树森林,点带点权,求其独立集的权值最大值。

为什么是无向基环树森林呢?因为一个骑士和他痛恨的骑士无论如何都不会一起被选上了,所以即使设前者也被后者痛恨也是没有影响的。

显然基环树之间是独立的,所以考虑在一棵基环树上面跑最大独立集。

很容易发现这道题的弱化版是 P1352 没有上司的舞会,大家可以先去做一下,这对独立思考此题至关重要。


好的,默认大家已经做完了。考虑从树上 dp 扩展到基环树上 dp。一般地,设深搜树上导致有环的那条回边为 u p → d n up \to dn updn,其中 u p up up 深度更低。

题解:P2607 [ZJOI2008] 骑士_第1张图片

发现基环树只比普通的树上增加了一个额外的限制: u p up up d n dn dn 不能同时取。所以当我们计算 u p up up 的 dp 值的时候,还需要考虑 d n dn dn 选了没选。

因为 d p dp dp 的状态制度是一直沿用的(也就是你一开始设了 dp[n][4] 以后也需要考虑其他点的 dp[n][4]),所以所有包含 d n dn dn 的子树需要同时记录 子树的根结点取不取 和 d n dn dn 结点取不取。

于是这个时候就得出来了 d p dp dp 的状态:

d p i , 0 dp_{i,0} dpi,0 d p i , 1 dp_{i,1} dpi,1 表示不取 d n dn dn 的情况下, i i i 取 / 不取的最大答案。

d p i , 2 dp_{i,2} dpi,2 d p i , 3 dp_{i,3} dpi,3 表示取 d n dn dn 的情况下, i i i 取 / 不取的最大答案。

这个状态的表示已经足够全面:因为基环树的独立集只比树上的独立集多了一个东西。


显然,有时候如果 i i i 不在环上,也不在 u p up up 的祖先,即 i i i 的子树里面没有 d n dn dn,就需要进行一些初始化的约定(因为这个时候的 i i i 完全与多出来的限制无关,只需要求解两个状态就行了):

这时候就设 d p i , 1 = d p i , 3 dp_{i,1} = dp_{i,3} dpi,1=dpi,3,以及 d p i , 2 = d p i , 0 dp_{i,2} = dp_{i,0} dpi,2=dpi,0 即可。

另外,还有一个情况,也就是 i = d n i=dn i=dn 的情况:显然这个时候 d p i , 1 dp_{i,1} dpi,1 d p i , 2 dp_{i,2} dpi,2 是完全无意义的,要设为负无穷大。


考虑推导转移方程。

显然,在一开始对于所有的 i i i d p i , 0 = d p i , 2 = 0 dp_{i,0} = dp_{i,2} = 0 dpi,0=dpi,2=0 d p i , 1 = d p i , 3 = v a l i dp_{i,1} = dp_{i,3} = val_i dpi,1=dpi,3=vali。其中 v a l i val_i vali 表示点权。

对于 i = d n i=dn i=dn d p i , 1 = d p i , 2 = − ∞ dp_{i,1} = dp_{i,2} = -\infty dpi,1=dpi,2=

对于 i ≠ d n , i ≠ u p i \not = dn,i \not = up i=dn,i=up,枚举其子结点 x x x(没有被访问过):

  • 不取环底, i i i 也不取: d p i , 0 + max ⁡ ( d p x , 0 , d p x , 1 ) → d p i , 0 dp_{i,0}+\max(dp_{x,0},dp_{x,1}) \to dp_{i,0} dpi,0+max(dpx,0,dpx,1)dpi,0。(自己都不取了,子结点取不取当然随意)

  • 不取环底, i i i 取: d p i , 1 + d p x , 0 → d p i , 1 dp_{i,1}+dp_{x,0} \to dp_{i,1} dpi,1+dpx,0dpi,1。(自己取了,子结点肯定不能取)

  • 显然因为这个时候 i i i d n dn dn 没有直接的联系,所以取环底和不取环底是一模一样的两种情况,即有:

    • d p i , 2 + max ⁡ ( d p x , 2 , d p x , 3 ) → d p i , 2 dp_{i,2}+\max(dp_{x,2},dp_{x,3}) \to dp_{i,2} dpi,2+max(dpx,2,dpx,3)dpi,2

    • d p i , 3 + d p x , 2 → d p i , 3 dp_{i,3}+dp_{x,2} \to dp_{i,3} dpi,3+dpx,2dpi,3

对于 i = u p i = up i=up,同样枚举其子结点 x x x(没有被访问过):

  • d p i , 2 = d p i , 3 = − ∞ dp_{i,2} = dp_{i,3} = -\infty dpi,2=dpi,3=

  • d p i , 0 + max ⁡ { d p x , 0 , d p x , 1 , d p x , 2 , d p x , 3 } → d p i , 0 dp_{i,0} + \max\{dp_{x,0},dp_{x,1},dp_{x,2},dp_{x,3}\} \to dp_{i,0} dpi,0+max{dpx,0,dpx,1,dpx,2,dpx,3}dpi,0

  • d p i , 1 + d p x , 0 → d p i , 1 dp_{i,1} + dp_{x,0} \to dp_{i,1} dpi,1+dpx,0dpi,1

对于答案,其就是对于每一个基环树的根结点 i i i,计算 max ⁡ { d p i , 0 , d p i , 1 , d p i , 2 , d p i , 3 } \max\{dp_{i,0},dp_{i,1},dp_{i,2},dp_{i,3}\} max{dpi,0,dpi,1,dpi,2,dpi,3} 即可。

#include 
#define int long long
using namespace std;
const int N = 1000010;
int n;
int val[N];
vector<int> v[N];
int dp[N][4];
bool stk[N], vis[N];
int up;

void dfs(int u, int pre) {
	vis[u] = stk[u] = 1;
	dp[u][1] = dp[u][3] = val[u], dp[u][0] = dp[u][2] = 0;
	for (auto i : v[u])
		if (!vis[i]) {
			dfs(i, u);
			if (u == up) {
				dp[u][0] += max(max(dp[i][0], dp[i][1]), max(dp[i][2], dp[i][3]));
				dp[u][1] += dp[i][0];
				dp[u][2] = dp[u][3] = -1e16;
			} else {
				dp[u][0] += max(dp[i][0], dp[i][1]);
				dp[u][1] += dp[i][0];
				dp[u][2] += max(dp[i][2], dp[i][3]);
				dp[u][3] += dp[i][2];
			}
		} else if (i != pre && stk[i])
			up = i, dp[u][1] = dp[u][2] = -1e16;
	stk[u] = 0;
}

signed main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int x;
		cin >> val[i] >> x;
		v[i].push_back(x), v[x].push_back(i);
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
		if (!vis[i])
			up = 0, dfs(i, 0), ans += max(max(dp[i][0], dp[i][1]), max(dp[i][2], dp[i][3]));
	cout << ans << endl;
	return 0;
}

一道蓝题做这么久,身败名裂!!!

你可能感兴趣的:(算法,学习,c++)