[IOI2014] Friend 朋友

复盘 pb 讲的题,来写篇题解造福社会

Description

给定 \(n\) 个点的点权 \(w_i\)\(n - 1\) 条关系,第 \(i\) 条关系如下:

  1. 给定 \(p\) ,让 \(i\)\(p\) 连边;

  2. 给定 \(p\) ,让 \(i\) 向所有和 \(p\) 有边的点连边;

  3. 给定 \(p\) ,让 \(i\)\(p\) 和所有和 \(p\) 有边的点连边。

求建出来的图中所有独立集的最大点权和。

\(n\ \leq 10 ^ 5,\ w_i\ \leq 10 ^ 6\)

Analysis

这种显然连的边数量是 \(O(n ^ 2)\) 级别的,肯定不能直接莽。

但是为什么我的第一思路却是 DS 优化建图啊喂, 虽然看上去确实有点像 Legacy ,但一来这不是区间,二来 3 操作着实有点迷惑。

但是题目要我们求的是最大独立集,也就是说我们可能不需要建图重新跑一遍,可以考虑在给定的条件上做文章。

Solution

那么,能够想到,设 \(ans_{i, 0/1}\) 表示前 \(i\) 个点,点 \(i\) 选或不选的独立集最大点权和。

现在就要想怎么让后面的条件加进来以后不会影响到当前的条件的贡献。

但是会发现假如我们直接正着处理的话,对于一个点 \(u\) 有多个父亲,父亲又没有直接连边,那贡献肯定不能边走边算了。

所以正着做是肯定是不行了,那么就会想要倒着做,加点改成删点。

感觉上其实还是有问题,假如一个点 \(v\) 和一个点 \(u\) 有个 2/3 操作,肯定是不能去把所有 \(u\) 的儿子的答案都更新一遍。但是所有儿子都要去更新点 \(u\) ,所以只要 \(v\) 的贡献先更新到了 \(u\) 身上就行了。

做法大概是确定了,画个图手膜一下, 3 种操作大概就是:

  1. 因为 \(p\)\(i\) 直接连边,所以要么 \(p\)\(i\) 不选,要么 \(p\) 不选 \(i\) 选,要么 \(p\) 不选 \(i\) 不选

  2. 注意 \(p\)\(i\) 之间没有边,所以两个点选或不选都可以,但是要注意 \(p\) 不选,\(i\) 选的情况是归类到 \(p\) 选的贡献里面,因为 \(p\) 的儿子是和 \(i\) 连了边的,本质上 \(p\)\(i\) 是一个道理。

  3. 比操作 2 多练了一条边,所以只是和操作 1 的方案一样,但是同时和操作 2 一样要注意贡献的归类

然后这么做下来,发现好像没有什么后效性,也没有什么其他要处理的,那就是做完了呀!!!

Code

/*

*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int ans[N][2];

int findSample(int n, int w[], int hos[], int pro[]) {
	for (int i = 0; i < n; ++i) {
		ans[i][1] = w[i];
	}
	for (int i = n - 1; i >= 1; --i) {
		int ans1 = ans[hos[i]][1] + ans[i][1];
		int ans2 = ans[hos[i]][1] + ans[i][0];
		int ans3 = ans[hos[i]][0] + ans[i][1];
		int ans4 = ans[hos[i]][0] + ans[i][0];
		if (pro[i] == 0) {
			ans[hos[i]][1] = ans2;
			ans[hos[i]][0] = max(ans3, ans4);
		}
		else if (pro[i] == 1) {
			ans[hos[i]][1] = max(max(ans1, ans2), ans3);
			ans[hos[i]][0] = ans4;
		}
		else {
			ans[hos[i]][1] = max(ans2, ans3);
			ans[hos[i]][0] = ans4;
		}
	}
	return max(ans[0][0], ans[0][1]);
}
posted @ 2021-11-03 09:59  Illusory_dimes  阅读(101)  评论(6编辑  收藏  举报