前言
今天和队友VP了24CCPC山东省赛,最后9题,但是赛中7题左右我就隐身了,赛后看题解发现E题不难,赛时过的人太少导致有点畏手畏脚,看到题解一下就懂了,几分钟写好。这里主要补一下E和L的题解,这场比赛学到了维护区间信息,可以考虑把区间挂在线段树节点上,以及动态维护树直径的典。
E 传感器 sensors
https://codeforces.com/gym/105385/problem/E
题意
给定 \(n\) 个球,初始都是红色,有 \(m\) 个区间。一共 \(n\) 轮,每轮将其中一个红球变蓝,强制在线维护恰好有一个红球的区间的编号平方和。
分析
一开始和队友都是读错题,以为小球是从蓝变红,这样就太简单了,因为每个区间只会恰好被操作2次后就剃掉,所以直接分块啥的乱搞就行。后来发现是从都是红色慢慢减少,这样每次小球变化,都得操作很多区间,但是很多操作是没必要的,只需要在意可能导致区间和变1的那些操作。
然后一个很巧妙的操作就是,类似线段树分治思想,把 \(m\) 个区间挂在线段树的 \(mlogn\) 个节点里,那么一个操作有可能使某个区间和变1,仅当某个线段树节点的区间和变成1或0.而这样的变化只会有2次,总共又只有 \(mlogn\) 的关键节点,所以就可以标记那些关键节点,线段树每个节点开vector,里面存挂在这个节点的区间编号。之后若干次单点修改,push_up时候判断一下限度桉树节点的区间和,如果变为1或0,就遍历一下对应vector里面的所有编号,里面对应区间的权值和要么是减去 \(r - l + 1 - 1\)要么减一,如果区间权值和变为1或0,就对应维护一下答案的编号平方和,代码很好写。
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define inf 0x3f3f3f3f
#define ll long long
#define pii pair<int, int>
#define tii tuple<int, int, int>
#define db double
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn = 5e5 + 10;
const int mod = 998244353;
int val[maxn];
ll ans;
struct SegmentTree {
struct Node {
int sum;
vector<int> vec;
};
#define ls rt << 1
#define rs rt << 1 | 1
#define mid ((l + r) >> 1)
vector<Node> t;
SegmentTree (int n) {t.resize(n << 2);}
void push_up(int rt) {t[rt].sum = t[ls].sum + t[rs].sum;}
void build(int rt, int l, int r) {
t[rt].vec.clear();
if (l == r) {t[rt].sum = 1;return;}
build(ls, l, mid), build(rs, mid + 1, r);
push_up(rt);
}
void modify(int rt, int l, int r, int p, int q, int id) {
if (p > r || q < l) return;
if (p <= l && r <= q) {
t[rt].vec.push_back(id);
return;
}
modify(ls, l, mid, p, q, id);
modify(rs, mid + 1, r, p, q, id);
}
void modify(int rt, int l, int r, int pos) {
if (l == r) {
t[rt].sum = 0;
for (auto id : t[rt].vec) {
val[id]--;
if (val[id] == 1) ans += id * id;
else if (val[id] == 0) ans -= id * id;
}
return;
}
if (pos <= mid) modify(ls, l, mid, pos);
else modify(rs, mid + 1, r, pos);
push_up(rt);
if (t[rt].sum == 1) {
for (auto id : t[rt].vec) {
val[id] -= (r - l + 1) - 1;
if (val[id] == 1) ans += id * id;
else if (val[id] == 0) ans -= id * id;
}
} else if (t[rt].sum == 0) {
for (auto id : t[rt].vec) {
val[id]--;
if (val[id] == 1) ans += id * id;
else if (val[id] == 0) ans -= id * id;
}
}
}
};
void solve() {
int n, m; cin >> n >> m;
SegmentTree seg(n);
seg.build(1, 0, n - 1);
for (int i = 1; i <= m; i++) {
int l, r; cin >> l >> r;
val[i] = r - l + 1;
if (val[i] == 1) ans += i * i;
seg.modify(1, 0, n - 1, l, r, i);
}
cout << ans << " ";
for (int i = 1; i <= n; i++) {
int x; cin >> x;
int pos = (x + ans) % n;
seg.modify(1, 0, n - 1, pos);
cout << ans << " \n"[i == n];
}
}
signed main() {
// freopen("1.in", "r", stdin);
// freopen("1.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}
L Intersection of Paths
https://codeforces.com/gym/105385/problem/L
题意
给定一棵树,每条边有边权,多次询问,每次询问临时改变一条边的边权,并给出参数 \(k\), 要求在树上选 \(k\) 条端点各不相同的路径,使得所有路径的交集部分的边权和最大。
分析
注意到对于一次询问的特定的 \(k\), 能选择的边,得满足它两侧连通块的点数都得大等于 \(k\), 才能选对应路径,包括上这边。那么把所有的合法边连起来,会形成一棵树。容易发现最终路径的交集肯定只能是一条链,所以问题等价于去找这棵树的最大边权和的链,也就是树的直径。然后关注到一个关键性质,这棵树的大小会随着 \(k\) 的变化单调变化,所以可以离线下来,将询问排序,对询问的 \(k\) 从小到大处理,等价于初始一棵完整的树,不断把边的 \(k\) 较小的删掉,然后维护树的直径,可以dfs把边的k也预处理出来,对边排序,然后用个指针维护,每次问到一个询问,判断指针处的边的 \(k\) 是否满足条件,不满足则删掉这条边,指针右移。
动态维护树直径CF1192B
然后就变成这个典题(也是看到题解说了才知道有这个典),直接看题解,大概就是利用了欧拉序性质,在边权都正的前提下,树的直径等价于 \(max(dep[l] + dep[r] - 2 \times dep[a]), l \leq a \leq r\) ,然后用线段树维护这个东西,合并时候按照区间最大最小值,以及 \(dep[l , r] - 2 \times dep[a]\) 的最大值来合并,整棵树的直径就是线段树1号节点的对应答案。然后边权的变化,就是欧拉序,让儿子的子树里深度全部加上一个变化量,即可。
1192B代码
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define inf 0x3f3f3f3f
#define ll long long
#define pii pair<int, int>
#define tii tuple<int, int, int>
#define db double
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn = 5e5 + 10;
const int mod = 998244353;
vector<pii> G[maxn];
int dfn[maxn], ncnt, dep[maxn], L[maxn], R[maxn], fa[maxn];
tii edge[maxn];
void dfs(int u, int f) {
dfn[++ncnt] = u;
L[u] = R[u] = ncnt;
fa[u] = f;
for (auto [v, w] : G[u]) {
if (v == f) continue;
dep[v] = dep[u] + w;
dfs(v, u);
dfn[++ncnt] = u;
R[u] = ncnt;
}
}
struct SegmentTree {
#define ls rt << 1
#define rs rt << 1 | 1
#define mid ((l + r) >> 1)
struct Node {
int ans, mx, mn, rm, lm, laz;
};
vector<Node> t;
SegmentTree(int n) {t.resize(n << 2);}
void push_up(int rt) {
t[rt].ans = max({t[ls].ans, t[rs].ans, t[ls].lm + t[rs].mx, t[rs].rm + t[ls].mx});
t[rt].mx = max(t[ls].mx, t[rs].mx);
t[rt].mn = min(t[ls].mn, t[rs].mn);
t[rt].lm = max({t[ls].lm, t[rs].lm, t[ls].mx - 2 * t[rs].mn});
t[rt].rm = max({t[ls].rm, t[rs].rm, t[rs].mx - 2 * t[ls].mn});
}
void fun(int rt, int l, int r, int k) {
t[rt].mx += k, t[rt].mn += k;
t[rt].lm -= k, t[rt].rm -= k;
t[rt].laz += k;
}
void push_down(int rt, int l, int r) {
if (t[rt].laz) {
fun(ls, l, mid, t[rt].laz);
fun(rs, mid + 1, r, t[rt].laz);
t[rt].laz = 0;
}
}
void build(int rt, int l, int r) {
if (l == r) {
t[rt].ans = 0;
t[rt].mx = t[rt].mn = dep[dfn[l]];
t[rt].rm = t[rt].lm = -dep[dfn[l]];
return;
}
build(ls, l, mid), build(rs, mid + 1, r);
push_up(rt);
}
void modify(int rt, int l, int r, int p, int q, int k) {
if (p > r || q < l) return;
if (p <= l && r <= q) {
fun(rt, l, r, k);
return;
}
push_down(rt, l, r);
modify(ls, l, mid, p, q, k), modify(rs, mid + 1, r, p, q, k);
push_up(rt);
}
};
void solve() {
int n, q, w; cin >> n >> q >> w;
for (int i = 0; i < n - 1; i++) {
int u, v, w; cin >> u >> v >> w;
G[u].emplace_back(v, w);
G[v].emplace_back(u, w);
edge[i] = tii(u, v, w);
}
dfs(1, 0);
SegmentTree seg(ncnt);
seg.build(1, 1, ncnt);
int lst = 0;
for (int i = 1; i <= q; i++) {
int d, e; cin >> d >> e;
d = (d + lst) % (n - 1);
e = (e + lst) % w;
auto& [u, v, prew] = edge[d];
if (fa[v] == u) swap(u, v);
int delta = e - prew;
prew = e;
seg.modify(1, 1, ncnt, L[u], R[u], delta);
lst = seg.t[1].ans;
cout << lst << "\n";
}
}
signed main() {
// freopen("1.in", "r", stdin);
// freopen("1.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
回到L,转化后跟这个典就没啥大区别了,比较巧妙的一个是删边等价于让边权等0,然后要注意的是,询问里临时改变边权,如果是已经删掉的边,那么不能改边权,否则可能让直径偏大。
L代码
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define inf 0x3f3f3f3f
#define ll long long
#define pii pair<int, int>
#define tii tuple<int, int, int>
#define db double
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn = 1e6 + 10;
const int mod = 998244353;
struct EDGE {
int u, v, w, k;
} edge[maxn];
vector<pii> G[maxn];
int ncnt, n, q, L[maxn], R[maxn], dfn[maxn], siz[maxn], dep[maxn], fa[maxn];
void dfs(int u, int f) {
siz[u] = 1, fa[u] = f;
dfn[++ncnt] = u;
L[u] = R[u] = ncnt;
for (auto[v, id] : G[u]) {
if (v == f) continue;
dep[v] = dep[u] + edge[id].w;
dfs(v, u);
dfn[++ncnt] = u;
R[u] = ncnt;
siz[u] += siz[v];
edge[id].k = min(siz[v], n - siz[v]);
}
}
struct SegmentTree {
#define ls rt << 1
#define rs rt << 1 | 1
#define mid ((l + r) >> 1)
struct Node {
int ans, mx, mn, lm, rm, laz;
};
vector<Node> t;
SegmentTree (int n) {t.resize(n << 2);}
void push_up(int rt) {
t[rt].ans = max({t[ls].ans, t[rs].ans, t[ls].lm + t[rs].mx, t[rs].rm + t[ls].mx});
t[rt].mx = max(t[ls].mx, t[rs].mx);
t[rt].mn = min(t[ls].mn, t[rs].mn);
t[rt].lm = max({t[ls].lm, t[rs].lm, t[ls].mx - 2 * t[rs].mn});
t[rt].rm = max({t[ls].rm, t[rs].rm, t[rs].mx - 2 * t[ls].mn});
}
void fun(int rt, int l, int r, int k) {
t[rt].mx += k, t[rt].mn += k;
t[rt].lm -= k, t[rt].rm -= k;
t[rt].laz += k;
}
void push_down(int rt, int l, int r) {
if (t[rt].laz) {
fun(ls, l, mid, t[rt].laz);
fun(rs, mid + 1, r, t[rt].laz);
t[rt].laz = 0;
}
}
void build(int rt, int l, int r) {
if (l == r) {
t[rt].ans = 0;
t[rt].mx = t[rt].mn = dep[dfn[l]];
t[rt].lm = t[rt].rm = -dep[dfn[l]];
return;
}
build(ls, l, mid), build(rs, mid + 1, r);
push_up(rt);
}
void modify(int rt, int l, int r, int p, int q, int k) {
if (p > r || q < l) return;
if (p <= l && r <= q) {
fun(rt, l, r, k);
return;
}
push_down(rt, l, r);
modify(ls, l, mid, p, q, k), modify(rs, mid + 1, r, p, q, k);
push_up(rt);
}
};
bool cmp(EDGE a, EDGE b) {return a.k < b.k;}
struct Query {
int a, b, k, id;
} query[maxn];
int ans[maxn];
tii preedge[maxn];
void solve() {
cin >> n >> q;
for (int i = 1; i < n; i++) {
int u, v, w; cin >> u >> v >> w;
G[u].emplace_back(v, i);
G[v].emplace_back(u, i);
edge[i] = EDGE{u, v, w, 0};
preedge[i] = tii(u, v, w);
}
dfs(1, 0);
SegmentTree seg(ncnt);
seg.build(1, 1, ncnt);
sort(edge + 1, edge + n, cmp);
for (int i = 1; i <= q; i++) {
int a, b, k; cin >> a >> b >> k;
query[i] = Query{a, b, k, i};
}
sort(query + 1, query + 1 + q, [](Query a, Query b) {return a.k < b.k;});
int lst = 1;
for (int i = 1; i <= q; i++) {
auto[a, b, k, id] = query[i];
while (lst < n && edge[lst].k < k) {
// 删除这条边
auto [u, v, w, k2] = edge[lst];
if (fa[v] == u) swap(u, v);
seg.modify(1, 1, ncnt, L[u], R[u], -w);
lst++;
}
auto[u, v, prew] = preedge[a];
if (fa[v] == u) swap(u, v);
if (min(siz[u], n - siz[u]) >= k) {
seg.modify(1, 1, ncnt, L[u], R[u], -prew);
seg.modify(1, 1, ncnt, L[u], R[u], b);
}
ans[id] = seg.t[1].ans;
if (min(siz[u], n - siz[u]) >= k) {
seg.modify(1, 1, ncnt, L[u], R[u], prew);
seg.modify(1, 1, ncnt, L[u], R[u], -b);
}
}
for (int i = 1; i <= q; i++)
cout << ans[i] << "\n";
}
signed main() {
// freopen("1.in", "r", stdin);
// freopen("1.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}