题解:CF2237H Slime and Queries
好题啊,感觉很多地方很难想清楚。
题意:现在有一个史莱姆在树上,其占据了一个大小为 \(m\) 的连通块。史莱姆在树上可以移动或者吃一个食物,移动需要 \(1\) 的代价,吃不消耗代价。移动的定义是,从连通块中扔掉一个点并加入一个新点,同时新的集合仍然为一个连通块。
现在有 \(q\) 个食物,你需要回答对于每个前缀的食物,史莱姆按顺序吃完的最少移动次数。按顺序的意思是,在前一个食物吃到之前,下一个食物不会刷新出来。同时这题强制在线,也就是你必须把一个前缀的答案算完之后才给出下一个食物位置。
做法:
我们发现,如果直接维护连通块的位置基本没法做,因为我们不知道后面的位置,所以导致我们需要一个点补过来的时候不知道扔掉哪一个,这样就增量不了。如果是不强制在线,贪心地可以证明扔掉下一次需要最晚地一定最优,这个不好快速维护,和本题其实关系也不大。
很容易想到延迟决策,我们先不决定删掉哪些点,只先把路径上的点先覆盖上代表我需要这些点在第 \(i\) 轮里面被覆盖。考虑讨论每个点是否可以节省贡献,那么节省贡献的条件就是我需要在上一次被覆盖到这一次之前一直占住这个位置,这样我就可以不必再把这个点覆盖一次,节省 \(1\) 的代价。
但是这里还有问题,我们需要满足同时存在的点不能多于 \(m\) 个,我们记 \(c_i\) 代表目前第 \(i\) 次覆盖的点,剩下了 \(c_i\) 次可以保留一个点,就是让他不动保持到 \(i+1\) 时。
为了方便,我们显然可以对目前的 \(c\) 取一个后缀 \(\min\),因为现在的条件等于我需要从前面一直保留到 \(i\),中间一个小的 \(c\) 会卡住前面传过来的次数。那么 \(c\) 就变成不降的了,我们考虑记 \(d\) 为 \(c\) 的差分,那么我们对于一个点 \(x\) 让他一直保留到现在,画画图发现,等于我们在前面一个大于零的 \(d\) 的位置减一。最后做完 \(i\) 这一轮后,我们对路径上打一个最后一次为 \(i\) 的标记,并且令 \(c_i=m\),因为他还可以保留 \(m\) 个位置,依次计算即可。
先不考虑正确性证明,对于维护这个东西,只需要把路径用树剖拆成 \(\log\) 个区间做颜色段均摊即可,找大于零的位置也是均摊的,选择你喜欢的数据结构维护,复杂度是 \(O(n\log^2n)\) 的。
现在还有最后一个问题,我们只对数量做了限制,为什么可以保证是连通块,我们考虑如果不是连通块会怎么样。我们发现,假设我们的连通块最后一步移动到一个点,以其为根的树,每个点上一次被覆盖的位置从根往叶子是单减的,所以如果有一个散点,那么你选择变成父亲只会变成一个更后的标记,\(c\) 数组上的决策不会更劣,所以就保证了一定会是连通块。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1e5 + 5;
struct Seg {
int l, r, v;
friend bool operator<(Seg x, Seg y) {
return (x.l != y.l ? x.l < y.l : x.r < y.r);
}
} ;
set<Seg> s;
int n, m, q, dfn[maxn], rev[maxn], tot, f[maxn], sz[maxn], son[maxn], dep[maxn], top[maxn];
vector<int> e[maxn];
void dfs1(int u, int fa) {
f[u] = fa, dep[u] = dep[fa] + 1; sz[u] = 1; son[u] = 0;
for (int i = 0; i < e[u].size(); i++) {
int v = e[u][i];
if(v == fa)
continue;
dfs1(v, u);
sz[u] += sz[v];
if(sz[son[u]] < sz[v])
son[u] = v;
}
}
void dfs2(int u, int t) {
top[u] = t; dfn[u] = ++tot; rev[tot] = u;
if(!son[u])
return ;
dfs2(son[u], t);
for (int i = 0; i < e[u].size(); i++) {
int v = e[u][i];
if(v == f[u] || v == son[u])
continue;
dfs2(v, v);
}
}
set<Seg>::iterator split(int x) {
set<Seg>::iterator it = s.lower_bound(Seg{x, 0, 0});
if(it != s.end() && it -> l == x)
return it;
it--;
if(it -> r < x)
return s.end();
int l = it -> l, r = it -> r, v = it -> v;
s.erase(it), s.insert(Seg{l, x - 1, v});
return s.insert(Seg{x, r, v}).first;
}
map<int, int> mp;
int renew(int pos, int val) {
int ans = 0;
// cout << pos << " " << val << " " << mp[0] << endl;
map<int, int>::iterator it = mp.upper_bound(pos);
if(it == mp.begin())
return 0;
for (map<int, int> ::iterator itp = it; ; itp--) {
if(itp == it)
itp--;
if(itp -> second > val) {
ans += val; itp -> second -= val;
itp++; mp.erase(itp, it);
return ans;
}
ans += itp -> second, val -= itp -> second;
// cout << ans << endl;
if(val == 0 || itp == mp.begin()) {
mp.erase(itp, it);
return ans;
}
}
}
int assign(int l, int r, int v) {
int sum = 0;
set<Seg>::iterator itr = split(r + 1), itl = split(l);
// cout << l << "adslfkj " << r << endl;
for (set<Seg>::iterator it = itl; it != itr; it++)
sum += renew(it -> v, it -> r - it -> l + 1);
s.erase(itl, itr);
s.insert(Seg{l, r, v});
return sum;
}
int upd_pth(int x, int y, int id) {
int ans = 0;
// cout << x << "adsfasf" << y << " " << top[x] << " " << top[y] << endl;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]])
swap(x, y);
int lx = dfn[top[x]], rx = dfn[x];
// cout << x << " " << top[x] << endl;
int val = assign(lx, rx, id);
mp[id] += val; ans += rx - lx + 1 - val;
x = f[top[x]];
}
if(dep[x] > dep[y])
swap(x, y);
int val = assign(dfn[x], dfn[y], id);
// cout << x << " " << y << "debug " << val << endl;
ans += dfn[y] - dfn[x] + 1 - val;
mp[id] += val;
return ans;
}
void solve() {
cin >> n >> m >> q;
for (int i = 1; i <= n; i++)
e[i].clear(); tot = 0;
for (int i = 1; i < n; i++) {
int x, y; cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
tot = 0;
dfs1(1, 0), dfs2(1, 1);
s.clear(), mp.clear();
s.insert(Seg{1, n, -1});
int lst = 0;
for (int i = 1; i <= m; i++) {
int x; cin >> x; lst = x;
assign(dfn[x], dfn[x], 0);
}
mp[0] = m;
// cout << "adf" << endl;
int ans = 0;
for (int i = 1; i <= q; i++) {
int p; cin >> p;
p = (p - 1 + ans) % n + 1;
// cout << p << "adsf" << endl;
ans += upd_pth(lst, p, i); lst = p;
cout << ans << " ";
}
cout << endl;
}
signed main() {
int T; cin >> T;
while(T--)
solve();
return 0;
}

浙公网安备 33010602011771号