2024CCPC山东省赛补题记录
前言
今天和队友VP了24CCPC山东省赛,最后9题,但是赛中7题左右我就隐身了,赛后看题解发现E题不难,赛时过的人太少导致有点畏手畏脚,看到题解一下就懂了,几分钟写好。这里主要补一下E和L的题解,这场比赛学到了维护区间信息,可以考虑把区间挂在线段树节点上,以及动态维护树直径的典。
E 传感器 sensors
https://codeforces.com/gym/105385/problem/E
题意
给定
分析
一开始和队友都是读错题,以为小球是从蓝变红,这样就太简单了,因为每个区间只会恰好被操作2次后就剃掉,所以直接分块啥的乱搞就行。后来发现是从都是红色慢慢减少,这样每次小球变化,都得操作很多区间,但是很多操作是没必要的,只需要在意可能导致区间和变1的那些操作。
然后一个很巧妙的操作就是,类似线段树分治思想,把
点击查看代码
#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
题意
给定一棵树,每条边有边权,多次询问,每次询问临时改变一条边的边权,并给出参数
分析
注意到对于一次询问的特定的
动态维护树直径CF1192B
然后就变成这个典题(也是看到题解说了才知道有这个典),直接看题解,大概就是利用了欧拉序性质,在边权都正的前提下,树的直径等价于
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理