主要是记录思路,不要被刚开始错误方向带偏了 www

「CF1110F」Nearest Leaf

特殊性质:先序遍历即为 \(1 \to n\),可得出:叶子节点编号递增或可在不改变树形态的基础上调整为递增。

这样就可找出区间 \([l, r]\) 中的叶子节点有哪些了,预处理深度,暴力 \(O(n ^ 2)\)。

考虑柿子 \(\min \{\mathrm{d} (y) - \mathrm{d} (\mathrm{f} (x))\}\),其中 \(d(x)\) 表示深度,\(f(x)\) 表示父亲,\(y\) 为枚举的叶子,\(x\) 为定点。

要使答案最小,即求 \(\min \{\mathrm{d}(y)\}\),这不线段树???哦 GG,区间 \([l, r]\) 显然有可能在定点 \(x\) 子树外。

那如果我们把离线询问挂树上,考虑动态维护线段树,保证遍历到一个节点时,线段树的长相是正确的,好像就能做了?即:

  • 遇到一个节点,我们就把子树内 \(-\),子树外 \(+\),然后直接处理查询。
  • 线段树维护区间加和区间最小值。
  • 注意极大值的取值,注意回溯的时候要复原。

过了。

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std; typedef long long LL;
LL Abs (LL x) { return x < 0 ? -x : x; }
LL Max (LL x, LL y) { return x > y ? x : y; }
LL Min (LL x, LL y) { return x < y ? x : y; } int Read () {
int x = 0, k = 1;
char s = getchar ();
while (s < '0' || s > '9') {
if (s == '-')
k = -1;
s = getchar ();
}
while ('0' <= s && s <= '9')
x = (x << 3) + (x << 1) + (s ^ 48), s = getchar ();
return x * k;
} void Write (LL x) {
if (x < 0)
putchar ('-'), x = -x;
if (x > 9)
Write (x / 10);
putchar (x % 10 + '0');
} void Print (LL x, char s) { Write (x), putchar (s); } const LL Inf = 1e16;
const int Maxn = 5e5 + 5; struct Segment_Tree {
struct Segment_Node {
int l, r;
LL Lazy, Val;
#define Lson p << 1
#define Rson p << 1 | 1
Segment_Node () {}
Segment_Node (int L, int R, LL La, LL V) { l = L, r = R, Lazy = La, Val = V; }
} Tr[Maxn << 2]; void Make_Tree (int p, int l, int r) {
Tr[p].Val = Inf, Tr[p].l = l, Tr[p].r = r, Tr[p].Lazy = 0;
if (l == r)
return ;
int Mid = (l + r) >> 1;
Make_Tree (Lson, l, Mid), Make_Tree (Rson, Mid + 1, r);
} void Pull (int p) { Tr[p].Val = Min (Tr[Lson].Val, Tr[Rson].Val); } void Push (int p) {
if (Tr[p].Lazy) {
Tr[Lson].Val += Tr[p].Lazy, Tr[Rson].Val += Tr[p].Lazy;
Tr[Lson].Lazy += Tr[p].Lazy, Tr[Rson].Lazy += Tr[p].Lazy;
Tr[p].Lazy = 0;
}
} void Update (int p, int l, int r, LL x) {
if (l <= Tr[p].l && Tr[p].r <= r) {
Tr[p].Lazy += x, Tr[p].Val += x;
return ;
}
Push (p);
int Mid = (Tr[p].l + Tr[p].r) >> 1;
if (l <= Mid)
Update (Lson, l, r, x);
if (r > Mid)
Update (Rson, l, r, x);
Pull (p);
} LL Query (int p, int l, int r) {
if (l <= Tr[p].l && Tr[p].r <= r)
return Tr[p].Val;
Push (p);
LL Res = Inf;
int Mid = (Tr[p].l + Tr[p].r) >> 1;
if (l <= Mid)
Res = Min (Res, Query (Lson, l, r));
if (r > Mid)
Res = Min (Res, Query (Rson, l, r));
return Res;
} #undef Lson
#undef Rson
} Tree; struct Query {
int l, r, Id;
Query () {}
Query (int L, int R, int I) { l = L, r = R, Id = I; }
}; struct Edge {
int v, w;
Edge () {}
Edge (int V, int W) { v = V, w = W; }
friend bool operator < (Edge x, Edge y) { return x.v < y.v; }
}; int Size[Maxn], n, m;
LL Res[Maxn], Dep[Maxn];
vector <Query> q[Maxn];
vector <Edge> Graph[Maxn]; void Add_Edge (int u, int v, int w) { Graph[u].push_back (Edge (v, w)); } void Dfs (int u) {
Size[u] = 1;
for (int i = 0, v; i < Graph[u].size (); i++)
v = Graph[u][i].v, Dep[v] = Dep[u] + Graph[u][i].w, Dfs (v), Size[u] += Size[v];
if (Graph[u].size () == 0)
Tree.Update (1, u, u, Dep[u] - Inf);
} void Calc (int u) {
for (int i = 0; i < q[u].size (); i++)
Res[q[u][i].Id] = Tree.Query (1, q[u][i].l, q[u][i].r);
for (int i = 0, v, w; i < Graph[u].size (); i++) {
v = Graph[u][i].v, w = Graph[u][i].w;
Tree.Update (1, v, v + Size[v] - 1, -w);
if (v > 1)
Tree.Update (1, 1, v - 1, w);
if (v + Size[v] - 1 < n)
Tree.Update (1, v + Size[v], n, w);
Calc (v);
Tree.Update (1, v, v + Size[v] - 1, w);
if (v > 1)
Tree.Update (1, 1, v - 1, -w);
if (v + Size[v] - 1 < n)
Tree.Update (1, v + Size[v], n, -w);
}
} int main () {
n = Read (), m = Read ();
for (int i = 2, p, w; i <= n; i++)
p = Read (), w = Read (), Add_Edge (p, i, w);
for (int i = 1; i <= n; i++)
sort (Graph[i].begin (), Graph[i].end ());
for (int i = 1, x, l, r; i <= m; i++)
x = Read (), l = Read (), r = Read (), q[x].push_back (Query (l, r, i));
Tree.Make_Tree (1, 1, n), Dfs (1), Calc (1);
for (int i = 1; i <= m; i++)
Print (Res[i], '\n');
return 0;
}

「CF633F」The Chocolate Spree

不相交的两条链的最大权值和,难道直接通过直径去扩展?或者直接硬 DP

考虑分讨四种情况:Dp[u][0/1/2/3] 分别表示(均在子树内)选了两条链 / 选了一条链 / 选了两条链,其中一条端点是 \(u\) / 选了一条链端点是 \(u\)。

以下转移均考虑遍历顺序(雾。

  • 对于 Dp[u][0]

    • \(v\) 选了两条,直接更新, Dp[u][0] = Max (Dp[u][0], Dp[v][0])
    • \(v\) 选了一条,故 \(u\) 再选一条, Dp[u][0] = Max (Dp[u][0], Dp[v][1] + Dp[u][1])
    • \(v\) 选了两条其中一条端点是 \(v\),扩展, Dp[u][0] = Max (Dp[u][0], Dp[v][2] + w[u])
    • \(v\) 选了一条且端点是 \(v\),扩展,Dp[u][0] = Max (Dp[u][0], Dp[v][3] + w[u] + Dp[u][1] - w[u])
    • 可以两条接起来一条,Dp[u][0] = Max (Dp[u][0], Max (Dp[v][2] + Dp[u][3], Dp[u][2] + Dp[v][3])
  • 对于 Dp[u][1]
    • \(v\) 选了一条,直接更新, Dp[u][1] = Max (Dp[u][1], Dp[v][1])
    • \(v\) 选了一条且端点是 \(v\),扩展, Dp[u][1] = Max (Dp[u][1], Dp[v][3] + w[u])
    • 可以两条接起来,Dp[u][0] = Max (Dp[u][0], Dp[v2][3] + Dp[v2][3] + w[u])
  • 对于 Dp[u][2]
    • \(v\) 选了一条,则 \(u\) 再选一条端点是 \(u\),Dp[u][2] = Max (Dp[u][2], Dp[v][1] + Dp[u][3])
    • \(v\) 选了两条且其中一条端点是 \(v\),扩展,Dp[u][2] = Max (Dp[u][2], Dp[v][2] + w[u])
    • \(v\) 选了一条且端点是 \(v\),扩展然后再选一条,或者不扩展再选一条端点为 \(u\),Dp[u][2] = Max (Dp[u][2], Max (Dp[v][3] + w[u] + Dp[u][1] - w[u], Dp[v][3] + Dp[u][3]))
  • 对于 Dp[u][3]
    • \(v\) 选了一条且端点是 \(v\),扩展,Dp[u][3] = Max (Dp[u][3], Dp[v][3] + w[u])

然后一阵删删改改过掉了,或许和上面的整理有点出入?看代码吧。

#include <cstdio>
#include <vector>
using namespace std; typedef long long LL;
LL Abs (LL x) { return x < 0 ? -x : x; }
LL Max (LL x, LL y) { return x > y ? x : y; }
LL Min (LL x, LL y) { return x < y ? x : y; } int Read () {
int x = 0, k = 1;
char s = getchar ();
while (s < '0' || s > '9') {
if (s == '-')
k = -1;
s = getchar ();
}
while ('0' <= s && s <= '9')
x = (x << 3) + (x << 1) + (s ^ 48), s = getchar ();
return x * k;
} void Write (LL x) {
if (x < 0)
putchar ('-'), x = -x;
if (x > 9)
Write (x / 10);
putchar (x % 10 + '0');
} void Print (LL x, char s) { Write (x), putchar (s); } const int Maxn = 1e5 + 5; int w[Maxn];
LL Dp[Maxn][4];
vector <int> Graph[Maxn]; void Add_Edge (int u, int v) { Graph[u].push_back (v), Graph[v].push_back (u); } void Tree_Dp (int u, int Fa) {
Dp[u][1] = Dp[u][3] = w[u];
LL Fir = 0, Sec = 0, Ma = 0;
for (int i = 0, v; i < Graph[u].size (); i++) {
v = Graph[u][i];
if (v == Fa)
continue;
Tree_Dp (v, u);
Dp[u][0] = Max (Dp[u][0], Dp[v][0]);
Dp[u][0] = Max (Dp[u][0], Dp[v][1] + w[u]);
Dp[u][0] = Max (Dp[u][0], Dp[v][2] + w[u]);
Dp[u][0] = Max (Dp[u][0], Dp[v][3] + w[u]);
if (i > 0) {
Dp[u][0] = Max (Dp[u][0], Dp[v][1] + Dp[u][1]);
Dp[u][0] = Max (Dp[u][0], Dp[v][1] + Dp[u][3]);
if (Ma)
Dp[u][0] = Max (Dp[u][0], Ma + w[u] + Dp[v][3]);
Dp[u][0] = Max (Dp[u][0], Dp[v][2] + Dp[u][3]);
Dp[u][0] = Max (Dp[u][0], Dp[v][3] + Dp[u][1]);
Dp[u][0] = Max (Dp[u][0], Dp[v][3] + Dp[u][3]);
Dp[u][0] = Max (Dp[u][0], Dp[v][3] + Dp[u][2]);
}
Dp[u][2] = Max (Dp[u][2], Dp[v][1] + w[u]);
Dp[u][2] = Max (Dp[u][2], Dp[v][2] + w[u]);
Dp[u][2] = Max (Dp[u][2], Dp[v][3] + w[u]);
if (i > 0) {
Dp[u][2] = Max (Dp[u][2], Dp[v][1] + Dp[u][3]);
if (Ma)
Dp[u][2] = Max (Dp[u][2], Ma + w[u] + Dp[v][3]);
Dp[u][2] = Max (Dp[u][2], Dp[v][3] + Dp[u][3]);
}
if (Dp[v][1] > Ma)
Ma = Dp[v][1];
if (Dp[v][3] > Fir)
Sec = Fir, Fir = Dp[v][3];
else if (Dp[v][3] > Sec)
Sec = Dp[v][3];
if (Fir && Sec)
Dp[u][1] = Max (Dp[u][1], Fir + Sec + w[u]);
Dp[u][1] = Max (Dp[u][1], Dp[v][1]);
Dp[u][1] = Max (Dp[u][1], Dp[v][3] + w[u]);
Dp[u][3] = Max (Dp[u][3], Dp[v][3] + w[u]);
}
} int main () {
int n = Read ();
for (int i = 1; i <= n; i++)
w[i] = Read ();
for (int i = 1, u, v; i < n; i++)
u = Read (), v = Read (), Add_Edge (u, v);
Tree_Dp (1, -1);
// for (int i = 1; i <= n; i++)
// printf ("%lld %lld %lld %lld\n", Dp[i][0], Dp[i][1], Dp[i][2], Dp[i][3]);
Print (Dp[1][0], '\n');
return 0;
}

「CF1120D」Power Tree

将所有叶子按 DFS 序拉成一个序列,记点 \(u\) 子树内最靠左的叶子为 \(l_u\),最靠右的叶子为 \(r_u\)。不难发现,题目中一次操作等价于在 \([l_u, r_u]\) 上做区间修改,也可转换为在差分序列上单点修改。

即是说对于点 \(u\),我们可以将 \(l_u\) 处加上 \(d\),在 \(r_u + 1\) 处减去 \(d\),目标是使所有点都为 \(0\)。

如果将 \(l_u\) 和 \(r_u + 1\) 进行连边,边权为 \(w_u\)。如果原树上所有节点的对应点都可以跑到新节点 \(n + 1\) 上,则一定可以得到符合题意的最终状态(逆向树上差分?

题目转换为求哪些点可能出现在无向图最小生成树上,Kruskal 判一下即可。

出现了!写并查集不写路径压缩的大憨憨!

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std; typedef long long LL;
int Abs (int x) { return x < 0 ? -x : x; }
int Max (int x, int y) { return x > y ? x : y; }
int Min (int x, int y) { return x < y ? x : y; } int Read () {
int x = 0, k = 1;
char s = getchar ();
while (s < '0' || s > '9') {
if (s == '-')
k = -1;
s = getchar ();
}
while ('0' <= s && s <= '9')
x = (x << 3) + (x << 1) + (s ^ 48), s = getchar ();
return x * k;
} void Write (LL x) {
if (x < 0)
putchar ('-'), x = -x;
if (x > 9)
Write (x / 10);
putchar (x % 10 + '0');
} void Print (LL x, char s) { Write (x), putchar (s); } const int Inf = 1e9;
const int Maxn = 2e5 + 5; struct Node {
int l, r;
Node () {}
Node (int L, int R) { l = L, r = R; }
} q[Maxn]; struct Edge {
int u, v, w, Pos;
Edge () {}
Edge (int U, int V, int W, int P) { u = U, v = V, w = W, Pos = P; }
friend bool operator < (Edge x, Edge y) { return x.w < y.w; }
} e[Maxn]; bool Vis[Maxn];
vector <int> Graph[Maxn];
int w[Maxn], Dfn[Maxn], Fa[Maxn], Cnt; void Add_Edge (int u, int v) { Graph[u].push_back (v), Graph[v].push_back (u); } void Make_Tree (int n) {
for (int i = 1; i <= n; i++)
Fa[i] = i;
} int Find_Set (int x) { return Fa[x] == x ? x : Fa[x] = Find_Set (Fa[x]); } void Dfs (int u, int Fa) {
q[u].l = Inf, q[u].r = -Inf;
for (int i = 0, v; i < Graph[u].size (); i++) {
v = Graph[u][i];
if (v == Fa)
continue;
Dfs (v, u), q[u].l = Min (q[u].l, q[v].l), q[u].r = Max (q[u].r, q[v].r);
}
if (Graph[u].size () == 1 && u != 1)
q[u].l = q[u].r = ++Cnt;
e[u] = Edge (q[u].l, q[u].r + 1, w[u], u);
} int main () {
int n = Read ();
for (int i = 1; i <= n; i++)
w[i] = Read ();
for (int i = 1, u, v; i < n; i++)
u = Read (), v = Read (), Add_Edge (u, v);
Dfs (1, -1), sort (e + 1, e + n + 1);
Make_Tree (Cnt + 1), Cnt++;
LL Res = 0;
int Tot = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = l;
while (r + 1 <= n && e[r].w == e[r + 1].w)
r++;
for (int i = l, u, v; i <= r; i++) {
u = Find_Set (e[i].u), v = Find_Set (e[i].v);
if (u != v)
Vis[e[i].Pos] = true, Tot++;
}
for (int i = l, u, v; i <= r; i++) {
u = Find_Set (e[i].u), v = Find_Set (e[i].v);
if (u != v)
Fa[v] = u, Res += e[i].w;
}
}
Print (Res, ' '), Print (Tot, '\n');
for (int i = 1; i <= n; i++)
if (Vis[i])
Print (i, ' ');
putchar ('\n');
return 0;
}

Solution -「树上杂题?」专练的更多相关文章

  1. LOJ6003 - 「网络流 24 题」魔术球

    原题链接 Description 假设有根柱子,现要按下述规则在这根柱子中依次放入编号为的球. 每次只能在某根柱子的最上面放球. 在同一根柱子中,任何2个相邻球的编号之和为完全平方数. 试设计一个算法 ...

  2. LOJ6002 - 「网络流 24 题」最小路径覆盖

    原题链接 Description 求一个DAG的最小路径覆盖,并输出一种方案. Solution 模板题啦~ Code //「网络流 24 题」最小路径覆盖 #include <cstdio&g ...

  3. LOJ6001 - 「网络流 24 题」太空飞行计划

    原题链接 Description 有个实验和个仪器,做实验有报酬买仪器有花费.每个实验都需要一些仪器,求最大净收益(实验报酬仪器花费),并输出一组方案. Solution 实验向所需仪器连边,实验的点 ...

  4. Libre 6006 「网络流 24 题」试题库 / Luogu 2763 试题库问题 (网络流,最大流)

    Libre 6006 「网络流 24 题」试题库 / Luogu 2763 试题库问题 (网络流,最大流) Description 问题描述: 假设一个试题库中有n道试题.每道试题都标明了所属类别.同 ...

  5. loj #6122. 「网络流 24 题」航空路线问题

    #6122. 「网络流 24 题」航空路线问题 题目描述 给定一张航空图,图中顶点代表城市,边代表两个城市间的直通航线.现要求找出一条满足下述限制条件的且途经城市最多的旅行路线. 从最西端城市出发,单 ...

  6. [LOJ#6002]「网络流 24 题」最小路径覆盖

    [LOJ#6002]「网络流 24 题」最小路径覆盖 试题描述 给定有向图 G=(V,E).设 P 是 G 的一个简单路(顶点不相交)的集合.如果 V 中每个顶点恰好在 P 的一条路上,则称 P 是  ...

  7. liberOJ#6006. 「网络流 24 题」试题库 网络流, 输出方案

    #6006. 「网络流 24 题」试题库     题目描述 假设一个试题库中有 n nn 道试题.每道试题都标明了所属类别.同一道题可能有多个类别属性.现要从题库中抽取 m mm 道题组成试卷.并要求 ...

  8. LOJ6000 - 「网络流 24 题」搭配飞行员

    原题链接 题意简述 求二分图的最大匹配. 题解 这里写的是匈牙利算法. 表示节点的当前匹配. 为真表示在这一轮匹配中,无法给节点一个新的匹配.所以如果为真就不用再dfs它了,直接continue就好. ...

  9. LibreOJ #6014. 「网络流 24 题」最长 k 可重区间集

    #6014. 「网络流 24 题」最长 k 可重区间集 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据   ...

随机推荐

  1. Intel CPU平台和架构介绍

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 服务器主板上数据传输流依次为CPU .内存.硬盘和网卡, ...

  2. Linux 查询 OS、CPU、内存、硬盘信息

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 一.前言 当我们接手了一台或者几台服务器的时候,首先我们 ...

  3. 现有教学数据库JX_DB,作业

    现有教学数据库JX_DB,数据库有以下三个基本表: 学生表student,它由学号sno.姓名sname.性别sex.出生日期Bdate.所在系dept五个属性构成.其中,学号不能为空,值是唯一的: ...

  4. iOS全埋点解决方案-时间相关

    前言 ​ 我们使用"事件模型( Event 模型)"来描述用户的各种行为,事件模型包括事件( Event )和用户( User )两个核心实体.我们在描述用户行为时,往往只需要描述 ...

  5. WSL2+Docker+IDEA一站式开发调试

    WSL2+Docker+IDEA一站式开发调试 前言 ​ 我们知道,Docker是一个容器引擎:对于开发者来说,使用Dokcer容器部署各种开发需要的中间件(比如myql.redis)会非常简单方便: ...

  6. 组织:ISO

    国际标准化组织(ISO)是一个全球性的非政府组织,成立于1947年,总部位于瑞士日内瓦. 该组织负责绝大部分领域(包括军工.石油.船舶等垄断行业)的标准化活动,中国是其正式成员,代表中国参加的国家机构 ...

  7. 每天一个 HTTP 状态码 100

    100 Continue 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分:指示客户端应该继续当前请求:如果请求已经完成,客户端可以忽略该响应. 常用于服务器已经接受了请求头,客户端应该继续 ...

  8. 第06组 Alpha冲刺 (1/6)

    1.1 基本情况 队名:守护(发际)线程 组长博客:郝雷明 作业博客:郝雷明 组员人数:10 1.2 冲刺概况汇报 1. 郝雷明(组长) 过去两天完成了哪些任务 学习了微信开发平台的文档内容,熟悉微信 ...

  9. consul系列文章02---替换掉.netcore的配置文件

    如果是开发微服务的项目,多个服务的配置管理起来比较麻烦,需要集中管理,也就是需要有配置中心: consul集成配置中心的思路:读取配置文件时不在从本地的应用中读取,而是从consul的KEY/valu ...

  10. Vue出现Component template should ...

    当运行vue出现错误Component template should contain exactly one root element. If you ...的时候,我们只需要将<templa ...