题意

  • 给定一棵有 \(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. 什么是RPC? (全面了解)

    一:RPC 1.什么是RPC? RPC 是指远程过程调用,也就是说两台服务器,A 和 B,一个应用部署在A 服务器上,想要调用B 服务器上应用提供的函数或方法,由于不在一个内存空间,不能直接调用,需要 ...

  2. 3D视觉算法初学概述

    背景知识 RGB-D相机 一,基于3DMM的三维人脸重建技术概述 1.1,3D 人脸重建概述 1.2,初版 3DMM 二,视觉SLAM算法基础概述 2.1,视觉里程计 2.2,后端优化 2.3,回环检 ...

  3. python进阶之路7 数据类型的内置方法

    内容回顾 while 循环补充说明 1.死循环 2.while循环嵌套和全局标志位 for循环 1.for 变量名 in 待遍历数据 for循环体代码 2.for 也可以与break continue ...

  4. Vue 中 Promise 的then方法异步使用及async/await 异步使用总结

    转载请注明出处: 1.Promise 的 then 方法使用 then 方法是 Promise 中 处理的是异步调用,异步调用是非阻塞式的,在调用的时候并不知道它什么时候结束,也就不会等到他返回一个有 ...

  5. [LeetCode]二进制求和

    题目 代码 class Solution { public: string addBinary(string a, string b) { int lenA = a.length(); int len ...

  6. [cocos2d-x]关于Action

    Action的分类 第一种:FiniteTimeAction类:有限时间的动作类 第二种:Follow类:节点跟随另一种节点的类 第三种:Speed类:节点执行速度类 第一种有限时间的动作类又分为瞬时 ...

  7. 《HelloGitHub》第 82 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. https://github.com/521xueweiha ...

  8. 常见非指纹built-in函数

    unescape unescape() _函数_可对通过 escape() 编码的字符串进行解码. unescape("abcdefg") 'abcdefg' unescape(& ...

  9. MRS_开发编译与设置相关问题汇总

    解决问题如下: MRS开发编译时,如何修改工程优化等级 MRS进行工程编译时,如何配置FLASH.RAM显示占比 打印浮点类型 配置LD文件在工程中显示 使用sprintf打印 当重复多次调用相同函数 ...

  10. 12月12日内容总结——Django之数据增删改查、Django请求生命周期流程图、Django路由层(路由匹配、转换器、正则匹配)、反向解析

    目录 一.可视化界面之数据增删改查 二.django请求生命周期流程图 三.django路由层 1.路由匹配 2.转换器 3.正则匹配 不同版本的区别 正则匹配斜杠导致的区别 4.正则匹配的无名有名分 ...