「2020-2021 集训队作业」大鱼治水
「2020-2021 集训队作业」大鱼治水
Part 1
60次操作。想到重链剖分。
初始钦定选择每条重边,对于每一个到根的路径上,如果是重边就不管,否则先用两次操作变成合法的,再扭回去。
这样子就是对于点到根的路径上每一条重边需要0次操作,轻边需要4次操作。
怎么想?
-
注意到对于每个轻边,我们扭过来在扭过去一共需要4次操作,也就是需要大概 $4 \log N$ 次操作,注意到刚好等于60.
-
注意到 $q \leq 500000$ 所以时间复杂度大概是一个 $O(q \log n)$ 便可以联想到树剖
Part2
40次操作。
感觉一下上面的做法,对于重儿子很“重”的情况自然是很优的。但是如果重儿子不是那么重,那么就一点也不优。
考虑到设 $f_i$ 表示以 $i$ 为根的子树的答案(这里只需满足到 $i$ 的路径上都是染色边即可)。我们重新定义重儿子,定义重儿子为节点 $u$ 的儿子中 $f$ 值最大的儿子。对于节点 $u$ 我们假设对于它的所有儿子的 $f$ 值有 $f_i \geq f_{i+1}$。按照 Part1 的方法,设 $m$ 为 $u$ 的子节点数量,即 $f_u = \max\{f_1 ,\max \limits_{j = 2} \limits^{j \leq m} f_j + 4\}$
但是考虑到我们也可以直接暴力做这个东西,即每次操作先钦点选边,然后取消这个钦定,则 $f_u = \max\{f_1 + 2 ,\max \limits_{j = 2} \limits^{j \leq m} f_j + 2\}$
比较上面两种方案,我们可以得到每个点的具体策略,本地打表算一下最多40次操作。亲测59pts。
Part 3
35次操作。
再一次细化平衡策略!注意到轻边和重边需要的额外操作数量总和为4.于是考虑是不是有可能重边1次操作,轻边3次操作。考虑对于每一次重边,钦定它,用一次操作。对于每一个轻边,取消钦点,钦定它,然后取消,3次操作。
则 $f_u = \max\{f_1 + 1 ,\max \limits_{j = 2} \limits^{j \leq m} f_j + 3\}$
本地打表算一下,35次操作,可过。
// Problem: #3390. 「2020-2021 集训队作业」大鱼治水
// Contest: LibreOJ
// URL: https://loj.ac/p/3390
// Memory Limit: 512 MB
// Time Limit: 3000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
// #include <vector>
#include"river.h"
namespace my{
const long long inf = 1e18;
const int mininf = 1e9 + 7;
#define pb push_back
const int MAX = 5e4 + 10;
std::vector <int> g[MAX];
int f[MAX];
int siz[MAX], son[MAX], dep[MAX];
int Fa[MAX];
int top[MAX], dfn[MAX], op[MAX], dfx[MAX];
int clk;
bool f2[MAX];
void dfs(int u, int fa){
dep[u] = dep[fa] + 1;
Fa[u] = fa;
siz[u] = 1;
int maxn = 0;
for(int i = 0; i < g[u].size(); i++){
int v = g[u][i];
if(v == fa) continue;
dfs(v, u);
if(f[v] >= f[maxn] or maxn == 0) maxn = v;
}
son[u] = maxn;
if(!son[u]){
op[u] = 0;
return ;
}
int j = son[u];
int cl3 = 0;
for(int i = 0; i < g[u].size(); i++){
int v = g[u][i];
if(v == Fa[u] or v == j) continue;
f[u] = std::max(f[u], f[v] + 4);
cl3 = std::max(cl3, f[v] + 3);
}
cl3 = std::max(cl3, f[j] + 1);
f[u] = std::max(f[u], f[j]);
if(cl3 < f[u] and cl3 < f[j] + 2){
op[u] = 3;
f[u] = cl3;
f2[u] = 0;
son[u] = maxn;
}
else if(f[u] > f[j] + 2){
f[u] = f[j] + 2;
op[u] = 2;
son[u] = 0;
}else{
op[u] = 1;
}
}
void dfs2(int u, int topu){
dfn[u] = ++clk;
dfx[clk] = u;
top[u] = topu;
int pre2;
if(op[u] == 3){
pre2 = son[u];
son[u] = 0;
}
if(son[u] and op[u] != 3) dfs2(son[u], topu);
for(int i = 0; i < g[u].size(); i++){
int v = g[u][i];
if(v == Fa[u] or v == son[u]) continue;
dfs2(v, v);
}
if(op[u] == 3){
son[u] = pre2;
}
}
std::vector<int> init(int n, std::vector<int> father){
for(int i = 2; i <= n; i++){
g[father[i - 2]].pb(i);
}
dfs(1, 1);
dfs2(1, 1);
std::vector <int> re;
// std::re.clear();
for(int i = 1; i <= n; i++){
if(op[i] == 2 or op[i] == 3) re.pb(0ll);
else re.pb(son[i]);
}
return re;
}
void solve(int x){
int pre2 = x;
while(x != 1 and top[x] != 1){
x = top[x];
int pre = x;
x = Fa[x];
if(op[x] == 3){
if(pre == son[x] and !f2[x]){
set(x, pre);
f2[x] = 1;
}else if(pre != son[x]){
if(f2[x]) set(x, 0), f2[x] = 0;
set(x, pre);
}
}
else if(op[x] == 1){
if(pre == son[x]){
continue;
}
set(x, 0);
set(x, pre);
}else{
set(x, pre);
}
}
wait();
x = pre2;
while(x != 1 and top[x] != 1){
x = top[x];
int pre = x;
x = Fa[x];
if(op[x] == 3){
if(pre == son[x]){
}else{
set(x, 0);
f2[x] = 0;
}
}
else if(op[x] == 1){
if(pre == son[x]) continue;
set(x, 0);
set(x, son[x]);
}else{
set(x, 0);
}
x = top[x];
}
return ;
}
}
std::vector<int> init(int n, std::vector<int> fa) {
return my::init(n, fa);
}
void solve(int x){
my::solve(x);
}
Part4?
注意到轻边和重边需要的额外操作数量总和为4,所以好像不是很有可能优化下去了?
总而言之,这是一道做了和做了一样的题。