题意

  • 给定一棵有 \(n\) 个结点的树,点有点权;一共有 \(m\) 次操作,每次操作包括以下两种:
  1. 在一个点的子树中删去一些结点,使得该子树中所有叶结点与该子树的根结点不连通,并且使删去的点的点权和最小,求出这个最小值。
  2. 修改一个点的点权。
  • \(n \le 2 \times 10^5\) ,\(m\) 与 \(n\) 同级 (题目中没有说明QAQ)

题目分析

显然是动态 DP。

据说这题珂以线段树二分但是我太蒻只会暴力 DDP

虽然题目中没有说明,但是这棵树以 \(1\) 号结点为根。

如果没有修改操作,这是一道简单的树形动规。

首先考虑没有修改操作的情况。

记 \(w_u\) 表示点 \(u\) 的权值, \(f_u\) 表示以 \(u\) 为根的子树的最小答案。

那么显然有

\[f_u = \min(w_u,\ \sum \limits _{v\in son_u} f_v)
\]

方程的意义为:要么删去结点 \(u\) ,要么不删去结点 \(u\) ,在结点 \(u\) 的孩子中删去结点,两者取最小。

特殊的,对于叶结点,\(f_u = w_u\) 。

然后考虑修改点权的操作。

注意到每次修改点权只会对该结点到根的路径上的 \(f\) 值产生影响。

所以每次修改时暴力从该结点一直修改到 \(1\) 号结点。

最坏时间复杂度 $ \Theta (mn)$ ,稳稳地 TLE 。

接下来就需要请出主角:动态 DP 了。

动态 DP

动态 DP 用于解决一类被套上修改点权操作的基础 DP 题目。

其主要思想就是利用线段树维护每个结点的 DP 值。

但是由于大多数状态转移方程不满足结合律,所以无法直接使用线段树维护。

但是我们知道矩阵乘法运算是满足结合律的。

所以我们就需要把状态转移方程用矩阵乘法的形式表达出来。

模板题相信大家都过了。

不会动态 DP 的同学可以先去写一下这道模板题,里面的题解介绍的很详细。

构造矩阵

终于回到这题了。

让我们再来玩一玩刚才的状态转移方程。

\[f_u = \min(w_u,\ \sum \limits _{v\in son_u} f_v)
\]

由于这是一棵树,所以先对它进行重链剖分,然后 按照套路 把轻儿子的答案的总和拎出来;也就是说,记 \(g_u\) 表示以 \(u\) 为根的子树中轻儿子的答案总和,则可以将原方程转化为:

\[f_u = \text{min}(w_u,\ f_{son_u}+g_u)
\]

然后考虑把这个方程转化为矩阵乘法的形式。

在这里,我们需要重新定义一下矩阵乘法:对于矩阵 \(A\) ,\(B\) ,若矩阵 \(C\) 满足 \(C=A*B\) ,则有:

\[C_{i,j}=\min \limits _k (A_{i,k}+B_{k,j})
\]

至于这个运算为什么满足结合律,请读者自行验证。

那么我们相当于要找到一个转移矩阵 \(U\) ,使得 :

\[\begin{pmatrix} f_{son_u} \\ 0 \end{pmatrix} * U = \begin{pmatrix} f_{u} \\ 0 \end{pmatrix}
\]

注意新定义的矩阵乘法不满足交换律。说的好像原来的矩阵乘法满足交换律一样。

首先 \(U\) 是一个 \(2 \times2\) 的矩阵。

根据新定义的矩阵乘法,可以得出:

  1. \(f_u=\min(f_{son_u}+U_{1,1}, 0+U_{1,2})\)

  2. \(0 = \min(f_{son_u}+U_{2,1}, 0+U_{2,2})\)

再结合原方程,即可得出

\[ \begin{pmatrix} f_{son_u} \\ 0 \end{pmatrix}
* \begin{pmatrix} g_u & w_u \\ \infty & 0 \end{pmatrix}
= \begin{pmatrix} f_{u} \\ 0 \end{pmatrix}\]

然后用线段树维护转移矩阵就可以了。

在实现时我使用了一个矩阵数组来记录转移矩阵,听说这样可以减常数。

另外由于我太蒻了,写的树剖常数大,所以这份代码需要吸氧才能通过。

Code

#include <cstdio>
#include <iostream>
#include <cstring> using namespace std; typedef long long ll;
const int MAXN = 200010;
const ll INF = 1e17+1; int n, m;; struct edge{
int ne, to;
}e[MAXN<<1];
int fir[MAXN], num = 0;
inline void join(int a, int b)
{
e[++num].ne = fir[a];
fir[a] = num;
e[num].to = b;
} struct mat{
ll ele[2][2];
mat(int type = 0)
{
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
ele[i][j] = INF;
if(type == 1) {
for(int i=0;i<2;i++)
ele[i][i] = 0;
}
}
ll& operator()(const int ix, const int iy){return ele[ix][iy];}
// 重定义的矩阵乘法
inline friend mat operator*(mat mx, mat my)
{
mat res(0);
for(int i=0;i<2;i++)
for(int k=0;k<2;k++)
for(int j=0;j<2;j++)
res(i,j)=min(res(i,j), mx(i,k)+my(k,j));
return res;
}
}; ll val[MAXN], f[MAXN];
mat g[MAXN]; int dep[MAXN], pa[MAXN], siz[MAXN], son[MAXN], top[MAXN], dfn[MAXN], rev[MAXN], end[MAXN], cnt = 0; void dfs1(int u, int fa)
{
siz[u] = 1; dep[u] = dep[fa] + 1; pa[u] = fa;
for(int i=fir[u];i;i=e[i].ne)
{
int v = e[i].to;
if(v == fa) continue;
dfs1(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int t)
{
dfn[u] = ++cnt; rev[cnt] = u; top[u] = t; end[t] = max(end[t], cnt);
g[u](0, 0) = 0; g[u](0, 1) = val[u];
g[u](1, 0) = INF; g[u](1, 1) = 0;
f[u] = 0;
if(son[u]) { dfs2(son[u], t);
f[u] += f[son[u]];
}
else g[u](0, 0) = INF; //为了保证叶结点转移的合法
for(int i=fir[u];i;i=e[i].ne)
{
int v = e[i].to;
if(v == pa[u] || v == son[u]) continue;
dfs2(v, v);
g[u](0, 0) += f[v]; //g 数组的转移不包括重儿子
f[u] += f[v];
}
if(!son[u]) f[u] = val[u]; // 特判叶结点
else f[u] = min(f[u], val[u]); // 记得两者取最小
}
struct {
int l, r;
mat val;
}t[MAXN<<2];
inline void pushUp(int k)
{
t[k].val = t[k<<1].val*t[k<<1|1].val;
}
void build(int l, int r, int k)
{
t[k].l = l; t[k].r = r;
if(l == r) {
t[k].val = g[rev[l]];
return ;
}
int mid = t[k].l+t[k].r>>1;
build(l, mid, k<<1);
build(mid+1, r, k<<1|1);
pushUp(k);
}
void update(int x, int k)
{
if(t[k].l == t[k].r) {
t[k].val = g[rev[x]]; //由于有在树外记录转移矩阵,直接赋值即可
return ;
}
int mid = t[k].l+t[k].r>>1;
if(x <= mid) update(x, k<<1);
else update(x, k<<1|1);
pushUp(k);
}
mat query(int x, int y, int k)
{
if(t[k].l == x && t[k].r == y) return t[k].val;
int mid = t[k].l+t[k].r>>1;
if(y <= mid) return query(x, y, k<<1);
else if(x >= mid+1) return query(x, y, k<<1|1);
else return query(x, mid, k<<1)*query(mid+1, y, k<<1|1);
}
inline void updatePath(int x, ll z)
{
val[x] += z;
g[x](0, 1) = val[x];
mat bef, aft;
while(x)
{
bef = query(dfn[top[x]], end[top[x]], 1);
// 记录更新之前的矩阵
update(dfn[x], 1);
aft = query(dfn[top[x]], end[top[x]], 1);
x = pa[top[x]];
g[x](0, 0) += min(aft(0, 0), aft(0, 1)) - min(bef(0, 0), bef(0, 1));
// 更新转移矩阵,为下一条链的修改做准备,注意这里需要先减去原先的值
}
}
inline ll querySubtree(int x)
{
mat res = query(dfn[x], end[x], 1); // 查询时只需要从当前点到链尾即可
return min(res(0, 0), res(0, 1));
} int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%lld",&val[i]);
for(int i=1;i<n;i++)
{
int a, b;
scanf("%d%d",&a,&b);
join(a, b);
join(b, a);
}
dfs1(1, 0);
dfs2(1, 1);
for(int i=1;i<=n;i++)
end[i] = end[top[i]];
build(1, n, 1);
scanf("%d",&m);
while(m--)
{
char opt; int x; ll z;
cin>>opt;
if(opt == 'C') {
scanf("%d%lld",&x,&z);
updatePath(x, z);
}
else {
scanf("%d",&x);
printf("%lld\n",querySubtree(x));
}
}
return 0;
}

洛谷 P6021 洪水的更多相关文章

  1. Solution -「洛谷 P6021」洪水

    \(\mathcal{Description}\)   Link.   给定一棵 \(n\) 个点的带点权树,删除 \(u\) 点的代价是该点点权 \(a_u\).\(m\) 次操作: 修改单点点权. ...

  2. [洛谷3457][POI2007]POW-The Flood

    洛谷题目链接:[POI2007]POW-The Flood 题意翻译 Description 你手头有一张该市的地图.这张地图是边长为 m∗n 的矩形,被划分为m∗n个1∗1的小正方形.对于每个小正方 ...

  3. 洛谷1640 bzoj1854游戏 匈牙利就是又短又快

    bzoj炸了,靠离线版题目做了两道(过过样例什么的还是轻松的)但是交不了,正巧洛谷有个"大牛分站",就转回洛谷做题了 水题先行,一道傻逼匈牙利 其实本来的思路是搜索然后发现写出来类 ...

  4. 洛谷P1352 codevs1380 没有上司的舞会——S.B.S.

    没有上司的舞会  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond       题目描述 Description Ural大学有N个职员,编号为1~N.他们有 ...

  5. 洛谷P1108 低价购买[DP | LIS方案数]

    题目描述 “低价购买”这条建议是在奶牛股票市场取得成功的一半规则.要想被认为是伟大的投资者,你必须遵循以下的问题建议:“低价购买:再低价购买”.每次你购买一支股票,你必须用低于你上次购买它的价格购买它 ...

  6. 洛谷 P2701 [USACO5.3]巨大的牛棚Big Barn Label:二维数组前缀和 你够了 这次我用DP

    题目背景 (USACO 5.3.4) 题目描述 农夫约翰想要在他的正方形农场上建造一座正方形大牛棚.他讨厌在他的农场中砍树,想找一个能够让他在空旷无树的地方修建牛棚的地方.我们假定,他的农场划分成 N ...

  7. 洛谷P1710 地铁涨价

    P1710 地铁涨价 51通过 339提交 题目提供者洛谷OnlineJudge 标签O2优化云端评测2 难度提高+/省选- 提交  讨论  题解 最新讨论 求教:为什么只有40分 数组大小一定要开够 ...

  8. 洛谷P1371 NOI元丹

    P1371 NOI元丹 71通过 394提交 题目提供者洛谷OnlineJudge 标签云端评测 难度普及/提高- 提交  讨论  题解 最新讨论 我觉得不需要讨论O long long 不够 没有取 ...

  9. 洛谷P1538迎春舞会之数字舞蹈

    题目背景 HNSDFZ的同学们为了庆祝春节,准备排练一场舞会. 题目描述 在越来越讲究合作的时代,人们注意的更多的不是个人物的舞姿,而是集体的排列. 为了配合每年的倒计时,同学们决定排出——“数字舞蹈 ...

  10. 洛谷八月月赛Round1凄惨记

    个人背景: 上午9:30放学,然后因为学校举办读书工程跟同学去书城选书,中午回来开始打比赛,下午又回老家,中间抽出一点时间调代码,回家已经8:50了 也许是7月月赛时“连蒙带骗”AK的太幸运然而因同学 ...

随机推荐

  1. vivo 服务端监控体系建设实践

    作者:vivo 互联网服务器团队- Chen Ningning 本文根据"2022 vivo开发者大会"现场演讲内容整理而成. 经过几年的平台建设,vivo监控平台产品矩阵日趋完善 ...

  2. JAVA中使用最广泛的本地缓存?Ehcache的自信从何而来 —— 感受来自Ehcache的强大实力

    大家好,又见面了. 本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面.如果感兴趣,欢迎关注以获取后续更新. 作为<深入理解缓存原理与实战设计 ...

  3. Cesium for Unreal加载倾斜摄影

    本文介绍UE4中通过Cesium插架加载本地倾斜摄影模型.Cesium for Unreal插件运行在UE环境何总运行Cesium,这样方便做一个GIS应用. 安装Cesium for Unreal插 ...

  4. 失配树学习笔记 | P5829 【模板】失配树

    简介 失配树(简称 Fail 树),是基于 KMP 的算法,可以高效的解决复杂的字符串前缀后缀关系问题. 前置知识: KMP 算法(求失配数组) 最近公共祖先(LCA) 希望大家看完这篇文章后可以理解 ...

  5. 就dispatch_source_t写的计时器

    直接上干货,我这里用的是Xcode12.4,macOS:11.2.3 OC版本:               swift版本:        

  6. (12)go-micro微服务JWT跨域认证

    目录 一 JWT介绍 二 JWT优缺点 三 JWT使用 1. 导包和数据定义 2.生成JWT 3.解析JWT 4.完整代码 四 最后 一 JWT介绍 JWT 英文名是 Json Web Token , ...

  7. 【3.x合批亲测】使用这个优化方案,iPhone6也能飞起来,直接拉满60帧!

    大家好,我是晓衡! 上周我花了3天的时间,体验测试了一款 Creator 3.x 性能优化工具:98K动态分层合批. 它能将 DrawCall 超过 1000+ 次的 2D 界面,实现运行时节点分层排 ...

  8. vs2019 内核驱动编译失败:网络共享盘问题

    怀念2008 我以为这个年代了,中文路径问题应该解决了,记得vs 2008写c的时候曾因中文路径的问题导致编译不通过,从2015版本开始发现有时候项目名称和路径里包含中文也可以编译通过了,习惯之后后面 ...

  9. Unity之屏幕自适应

    Unity之屏幕自适应 哪个小伙伴能告诉我为什么CSDN我上传资源之后,下载费用是5积分啊!?怎么改为0呢,小黑苦恼啊!!!!!!! 一.前言 先言归正传吧,今天小黑给大家带来一个非常不错的分享--屏 ...

  10. Blazor入门100天 : 身份验证和授权 (6) - 使用 FreeSql orm 管理ids数据

    目录 建立默认带身份验证 Blazor 程序 角色/组件/特性/过程逻辑 DB 改 Sqlite 将自定义字段添加到用户表 脚手架拉取IDS文件,本地化资源 freesql 生成实体类,freesql ...