3 春季大扫除 题解
春季大扫除
题面
小y要对一棵 \(n\) 个节点,边权为 1 的树进行大扫除,她清理的过程是这样的:
- 选中一对叶子节点
- 将选中的叶子结点间的路径清理,清理的代价即为路径长度
为了保护叶子,每个叶子节点都只能选一次
为了增加难度,小y对树进行了 \(m\) 改造,在第 \(i\) 次改造中,她在原始树的某些节点下添加了共 \(D_i\) 个节点!
对于每次改造,求将每条边都至少清理一次的最小代价;
注意:每次改造是独立的,每次都是在原始的树上进行改造
\(1 \le n ,m \le 10^5\)
\(1 \le \sum D_i \le 10^5\)
题解
手模样例发现,对于某个子树而言,如果子树内有奇数个叶子节点,那么子树根节点上面的这条边一定会经过一次,否则会经过两次,不会经过更多次,因为经过更多次一定不优,所以我们只需考虑这两种情况。
假设子树内叶子节点数为奇数的子树个数为 \(cnt\) ,不难发现,答案即为 \((n - 1) \times 2 - cnt\) ,也就是先假设每条边都经过两次,然后减去经过一次的边的数量即可。
那么如何动态维护这个叶子节点数为奇数的子树个数呢?
考虑插入一个点时,受影响的是从这个点到根节点上的点,所以我们可以用树剖来维护这个。
时间复杂度 \(O(n \log^2 n)\)
code
这道题还卡常,实现的一些细节要注意,避免常数过大
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
namespace michaele {
#define ls (p << 1)
#define rs (p << 1 | 1)
const int N = 1e5 + 10, M = N << 1;
int n, m;
int h[N], ver[M], ne[M], tot;
int du[N], qr[N], cnt_leaf;
bool leaf[N], vis[N];
pair <int, int> st[N * 20];
int tp;
inline void add (int x, int y) {
ver[ ++ tot] = y;
ne[tot] = h[x];
h[x] = tot;
}
struct node {
int val;
bool rev;
} t[N << 2];
inline void update (int p) { t[p].val = t[ls].val + t[rs].val; }
void build (int p, int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
build (ls, l, mid);
build (rs, mid + 1, r);
update (p);
}
inline void pushdown (int p, int l, int r) {
if (!t[p].rev) return;
int mid = (l + r) >> 1;
t[ls].rev ^= 1;
t[rs].rev ^= 1;
t[ls].val = mid - l + 1 - t[ls].val;
t[rs].val = r - mid - t[rs].val;
t[p].rev = 0;
}
void modify (int p, int l, int r, int x, int y) {
if (x <= l && r <= y) {
t[p].rev ^= 1;
t[p].val = r - l + 1 - t[p].val;
return;
}
pushdown (p, l, r);
int mid = (l + r) >> 1;
if (x <= mid) modify (ls, l, mid, x, y);
if (mid < y) modify (rs, mid + 1, r, x, y);
update (p);
}
int siz[N], dep[N], son[N], fa[N];
int id[N], top[N], tim;
void dfs1 (int x, int f) {
fa[x] = f;
siz[x] = 1;
dep[x] = dep[f] + 1;
for (int i = h[x]; i; i = ne[i]) {
int y = ver[i];
if (y == f) continue;
dfs1 (y, x);
siz[x] += siz[y];
if (siz[y] > siz[son[x]]) son[x] = y;
}
}
void dfs2 (int x, int ftop) {
id[x] = ++ tim;
top[x] = ftop;
if (!son[x]) return;
dfs2 (son[x], ftop);
for (int i = h[x]; i; i = ne[i]) {
int y = ver[i];
if (y == fa[x] || y == son[x]) continue;
dfs2 (y, y);
}
}
inline void change (int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap (x, y);
modify (1, 1, n, id[top[x]], id[x]);
st[ ++ tp] = {id[top[x]], id[x]};
x = fa[top[x]];
}
if (x == y) return;
if (dep[x] > dep[y]) swap (x, y);
modify (1, 1, n, id[son[x]], id[y]);
st[ ++ tp] = {id[son[x]], id[y]};
}
void solve () {
ios :: sync_with_stdio (0);
cin.tie (0);
cout.tie (0);
cin >> n >> m;
for (int i = 1; i < n; i ++) {
int x, y;
cin >> x >> y;
add (x, y);
add (y, x);
du[x] ++;
du[y] ++;
}
for (int i = 1; i <= n; i ++) {
if (du[i] == 1) {
leaf[i] = 1;
cnt_leaf ++;
} else leaf[i] = 0;
}
dfs1 (1, 0);
dfs2 (1, 1);
build (1, 1, n);
for (int i = 1; i <= n; i ++) {
if (leaf[i]) {
change (i, 1);
}
}
for (int i = 1; i <= m; i ++) {
int d;
cin >> d;
int now_n = n + d, now_leaf = cnt_leaf;
tp = 0;
for (int j = 1; j <= d; j ++) {
cin >> qr[j];
if (leaf[qr[j]] && !vis[qr[j]]) {
// 某个叶子第一次被顶替
vis[qr[j]] = 1;
} else {
change (qr[j], 1);
now_leaf ++;
}
}
int now_cnt = t[1].val + d;
if (now_leaf & 1) cout << -1 << endl;
else cout << (now_n - 1) * 2 - now_cnt << endl;
// 还原,被卡常了
for (int j = 1; j <= d; j ++) {
// change (v[j], 1);
if (leaf[qr[j]] && vis[qr[j]]) {
vis[qr[j]] = 0;
// change (v[j], 1);
}
}
// 用栈记录一下修改的区间,从而减少一个log
for (int j = 1; j <= tp; j ++) {
modify (1, 1, n, st[j].first, st[j].second);
}
}
}
}
int main () {
// freopen ("test/test.in", "r", stdin);
// freopen ("test/test.out", "w", stdout);
michaele :: solve ();
return 0;
}

浙公网安备 33010602011771号