前言
今天和隊友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;
}