4712: 洪水 基于链分治的动态DP

国际惯例的题面:

看起来很神的样子......如果我说这是动态DP的板子题你敢信?
基于链分治的动态DP?说人话,就是树链剖分线段树维护DP。
既然是DP,那就先得有转移方程。
我们令f[i]表示让i子树中的叶子节点全部与根不联通,所需要的最小代价,v[i]为输入的点权。
显然f[i]=min(v[i],sigma(f[soni])),边界条件是,如果i是叶子节点,则f[i]=v[i]。
我们需要用链分治去维护这个DP,所以要把DP拆成重链和轻链独立的形式。
我们还是用f[i]表示让i子树中的叶子节点全部与根不联通,所需要的最小代价,h[i]表示i的轻儿子的f值之和。
根据定义,h[i]=sigma(h[light_sons_i]),
同时我们有:f[i]=min(v[i],f[heavy_son_i]+h[i])。
这个转移我们能写成一个最短路矩阵相乘的形式:
{0,f[heavy_son_i]}*{{0,v[i]},{inf,h[i]}} = {0,f[i]}
(我的写法和C++多维数组的表示方法相同,inf表示不能转移)
显然(陈俊锟说过)这个矩阵连乘是具有结合性的,所以我们能用线段树维护一条链上转移矩阵的连乘积。
于是修改的时候,我们只需要从这个点一路改上去,在跳轻边的时候修改其父亲的h值和转移矩阵,然后线段树更新即可。
查询的话就查询这个点到其所在重链底端线段树上转移矩阵的连乘积,然后左乘一个初始化矩阵即可。
时间复杂度O(8nlog^2n),加上fread快读还是不慢(能看)的。
(其实分析题目性质能够让转移复杂度更低,然而不如矩阵的方法通用(反正就是个常数,不管他了)。关于分析性质的解法,见我BZOJ5210的题解)
代码:

  1 #pragma GCC optimize(2)
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<algorithm>
  5 #include<cctype>
  6 typedef long long int lli;
  7 const int maxn=2e5+1e2;
  8 const lli inf=0x3f3f3f3f3f3f3f3fll;
  9
 10 struct Matrix {
 11     lli dat[2][2];
 12     Matrix() { memset(dat,0x3f,sizeof(dat)); }
 13     lli* operator [] (const int &x) { return dat[x]; }
 14     const lli* operator [] (const int &x) const { return dat[x]; }
 15     friend Matrix operator * (const Matrix &a,const Matrix &b) {
 16         Matrix ret;
 17         for(int i=0;i<2;i++) for(int j=0;j<2;j++) for(int k=0;k<2;k++) ret[i][j] = std::min( ret[i][j] , a[i][k] + b[k][j] );
 18         return ret;
 19     }
 20 }trans[maxn],ini;
 21
 22 lli v[maxn],f[maxn],h[maxn];
 23 int s[maxn],t[maxn<<1],nxt[maxn<<1];
 24 int fa[maxn],siz[maxn],dep[maxn],son[maxn],top[maxn],id[maxn],rec[maxn],mxd[maxn],iid;
 25 int n;
 26
 27 struct SegmentTree {
 28     Matrix dat[maxn<<2];
 29     #define lson(pos) (pos<<1)
 30     #define rson(pos) (pos<<1|1)
 31     inline void build(int pos,int l,int r) { // Warning :: from right to left .
 32         if( l == r ) return void(dat[pos]=trans[rec[l]]);
 33         const int mid = ( l + r ) >> 1;
 34         build(lson(pos),l,mid) , build(rson(pos),mid+1,r) , dat[pos] = dat[rson(pos)] * dat[lson(pos)];
 35     }
 36     inline void update(int pos,int l,int r,const int &tar) {
 37         if( l == r ) return void(dat[pos]=trans[rec[l]]);
 38         const int mid = ( l + r ) >> 1;
 39         tar <= mid ? update(lson(pos),l,mid,tar) : update(rson(pos),mid+1,r,tar);
 40         dat[pos] = dat[rson(pos)] * dat[lson(pos)];
 41     }
 42     inline Matrix query(int pos,int l,int r,const int &ll,const int &rr) {
 43         if( ll <= l && r <= rr ) return dat[pos];
 44         const int mid = ( l + r ) >> 1;
 45         if( rr <= mid ) return query(lson(pos),l,mid,ll,rr);
 46         else if( ll > mid ) return query(rson(pos),mid+1,r,ll,rr);
 47         else return query(rson(pos),mid+1,r,ll,rr) * query(lson(pos),l,mid,ll,rr);
 48     }
 49 }sgt;
 50
 51 inline void addedge(int from,int to) {
 52     static int cnt = 0;
 53     t[++cnt] = to , nxt[cnt] = s[from]  , s[from] = cnt;
 54 }
 55 inline void pre(int pos) {
 56     siz[pos] = 1;
 57     for(int at=s[pos];at;at=nxt[at]) if( t[at] != fa[pos] ) {
 58         dep[t[at]] = dep[pos] + 1 , fa[t[at]] = pos , pre(t[at]) , siz[pos] += siz[t[at]];
 59         if( siz[t[at]] > siz[son[pos]] ) son[pos] = t[at];
 60     }
 61 }
 62 inline void dfs(int pos) {
 63     ++iid , mxd[top[rec[id[pos]=iid]=pos]=pos==son[fa[pos]]?top[fa[pos]]:pos] = iid , h[pos] = f[pos] = inf;
 64     if( son[pos] ) dfs(son[pos]) , f[pos] = f[son[pos]] , h[pos] = 0; // pos isn‘t a leaf node .
 65     for(int at=s[pos];at;at=nxt[at]) if( t[at] != fa[pos] && t[at] != son[pos] ) dfs(t[at]) , h[pos] += f[t[at]];
 66     f[pos] = std::min( f[pos] + h[pos] , v[pos] );
 67 }
 68
 69 inline lli query(int pos) {
 70     Matrix ret = sgt.query(1,1,n,id[pos],mxd[top[pos]]);
 71     ret = ini * ret;
 72     return ret[0][1];
 73 }
 74
 75 inline void update(int pos) {
 76     while(pos) {
 77         trans[pos][0][1] = v[pos] , trans[pos][1][1] = h[pos] ,
 78         sgt.update(1,1,n,id[pos]) , pos = top[pos];
 79         if( pos == 1 ) break; // root don‘t have fa .
 80         Matrix fs = ini * sgt.query(1,1,n,id[pos],mxd[pos]);
 81         h[fa[pos]] -= f[pos] , h[fa[pos]] += ( f[pos] = fs[0][1] );
 82         pos = fa[pos];
 83     }
 84 }
 85
 86 inline char nxtchar() {
 87     static const int BS = 1 << 21;
 88     static char buf[BS],*st=buf+BS,*ed=st;
 89     if( st == ed ) ed = buf + fread(st=buf,1,BS,stdin);
 90     return st == ed ? -1 : *st++;
 91 }
 92 inline char realchar() {
 93     char ret;
 94     while( !isalpha(ret=nxtchar()) );
 95     return ret;
 96 }
 97 inline int getint() {
 98     int ret = 0 , ch;
 99     while( !isdigit(ch=nxtchar()) );
100     do ret=ret*10+ch-‘0‘; while( isdigit(ch=nxtchar()) );
101     return ret;
102 }
103 inline int getlli() {
104     lli ret = 0 , ch;
105     while( !isdigit(ch=nxtchar()) );
106     do ret=ret*10+ch-‘0‘; while( isdigit(ch=nxtchar()) );
107     return ret;
108 }
109
110 int main() {
111     static int m;
112     n = getint() , ini[0][0] = ini[0][1] = 0;
113     for(int i=1;i<=n;i++) v[i] = getlli();
114     for(int i=1,a,b;i<n;i++) a = getint() , b = getint() , addedge(a,b) , addedge(b,a);
115     pre(1) , dfs(1);
116     for(int i=1;i<=n;i++) trans[i][0][0] = 0 , trans[i][0][1] = v[i] , trans[i][1][0] = inf , trans[i][1][1] = h[i];
117     sgt.build(1,1,n);
118     m = getint();
119     for(int i=1,o,p;i<=m;i++) {
120         o = realchar() , p = getint();
121         if( o == ‘Q‘ ) printf("%lld\n",query(p));
122         else if( o == ‘C‘ ) v[p] += getlli() , update(p);
123     }
124     return 0;
125 }

雪の降るこの街にも 暖かい光が差し
雪花飘飞的这条街道上 温暖光芒从天而降
折れた羽を癒してる 傷ついた心の奧
受伤心灵的深处 被祈愿之羽逐渐治愈
足音が聞こえてくる 明日への扉叩いて
我听到了谁的脚步声 有人在敲打通往明日的门扉
目の前の道を進む 季節が巡る 時の中で
季节更迭 我在时光之流中踏上眼前的道路
巡る想い あなただけ 笑顏が今もまぶしい
流连的思念 只有你的笑颜直到现在还如此炫目

原文地址:https://www.cnblogs.com/Cmd2001/p/9011712.html

时间: 2024-10-11 09:40:01

4712: 洪水 基于链分治的动态DP的相关文章

[bzoj4712] 洪水 [树链剖分+线段树+dp]

题面 传送门 思路 DP方程 首先,这题如果没有修改操作就是sb题,dp方程如下 $dp[u]=max(v[u],max(dp[v]))$,其中$v$是$u$的儿子 我们令$g[u]=max(dp[v])$ 修改? 我们发现,本题中所有的修改都是非负的 也就是说,每一次修改结束以后,$dp[u]$的值只增不减 同时,修改$u$位置的$v[u]$值,只会影响到$u$到根的这一条链上的$dp$值 我们考虑修改后,这条链上的每个点的$dp[u]$值的增量,令这个增量为$delta[u]$ 那么显然当$

bzoj 4712 洪水——动态DP

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4712 因为作为动态DP练习而找到,所以就用动态DP做了,也没管那种二分的方法. 感觉理解似乎加深了. 果然初始权值也都是非负的. 所以 dp[cr] 表示当前子树与自己的叶子都断开了的最小代价,则 dp[cr]=min{ sigma dp[v] , w[cr] }(v是cr的直接孩子). 但这样的话,修改的时候需要把自己到根的路径都走一遍.不过查询是O(1)的,所以考虑分配一下. 走到根的

UOJ268 [清华集训2016] 数据交互 【动态DP】【堆】【树链剖分】【线段树】

题目分析: 不难发现可以用动态DP做. 题目相当于是要我求一条路径,所有与路径有交的链的代价加入进去,要求代价最大. 我们把链的代价分成两个部分:一部分将代价加入$LCA$之中,用$g$数组保存:另一部分将代价加在整条链上,用$d$数组保存. 这时候我们可以发现,一条从$u$到$v$的路径的代价相当于是$d[LCA(u,v)]+\sum_{x \in edge(u,v)}g[x]$. 如果是静态的,可以用树形DP解决. 看过<神奇的子图>的同学都知道,叶子结点是从它的儿子中取两个最大的出来,所

「校内训练 2019-04-23」越野赛车问题 动态dp+树的直径

题目传送门 http://192.168.21.187/problem/1236 http://47.100.137.146/problem/1236 题解 题目中要求的显然是那个状态下的直径嘛. 所以这道题有一个非常简单的做法--线段树分治. 直接把每一条边按照 \(l, r\) 的区间放到线段树上进行分治,遍历的时候用并查集维护直径就可以了. 时间复杂度为 \(O(n\log^2n)\). 很早以前就写了这个算法,代码附在了最后,不多讲了. 但是这道题还有一个方法--动态 DP. 线段树分治

SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统

1.前言本文主要介绍使用SpringBoot与shiro实现基于数据库的细粒度动态权限管理系统实例. 使用技术:SpringBoot.mybatis.shiro.thymeleaf.pagehelper.Mapper插件.druid.dataTables.ztree.jQuery 开发工具:intellij idea 数据库:mysql.redis 2.表结构还是是用标准的5张表来展现权限.如下图:image 分别为用户表,角色表,资源表,用户角色表,角色资源表.在这个demo中使用了mybat

uoj#268. 【清华集训2016】数据交互(动态dp+堆)

传送门 动态dp我好像还真没咋做过--通过一个上午的努力光荣的获得了所有AC的人里面的倒数rk3 首先有一个我一点也不觉得显然的定理,如果两条路径相交,那么一定有一条路径的\(LCA\)在另一条路径上 于是我们可以对于每一个点记录两个值,一个\(a_i\)表示\(LCA\)在\(i\)点的所有路径的权值之和,一个是\(b_i\),表示经过点\(i\)且\(LCA\)不在点\(i\)的所有路径的权值之和 那么对于一条路径\((u,v)\),它的权值就是\(b_{LCA(u,v)}+\sum_{i\

LG4719 【模板】动态dp

题意 题目描述 给定一棵\(n\)个点的树,点带点权. 有\(m\)次操作,每次操作给定\(x,y\),表示修改点\(x\)的权值为\(y\). 你需要在每次操作之后求出这棵树的最大权独立集的权值大小. 输入输出格式 输入格式: 第一行,\(n,m\),分别代表点数和操作数. 第二行,\(V_1,V_2,...,V_n\),代表\(n\)个点的权值. 接下来\(n-1\)行,\(x,y\),描述这棵树的\(n-1\)条边. 接下来\(m\)行,\(x,y\),修改点\(x\)的权值为\(y\).

模版 动态 dp

模版 动态 dp 终于来写这个东西了.. LG 模版:给定 n 个点的数,点有点权, $ m $ 次修改点权,求修改完后这个树的最大独立集大小. 我们先来考虑朴素的最大独立集的 dp \[ dp[u][0] = \sum_v max\{dp[v][1],dp[v][0]\}\\dp[u][1] = val[u] + \sum_v dp[v][0] \] 现在我们就拥有了一个 $ O(nm) $ 的做法,但是明显它不优秀. 既然支持修改,我们可以考虑树剖(或者LCT),现在树被我们剖成了重链和轻链

动态dp和dp套dp

概述 这是两类特别的\(dp\)种类,分别是带修改的\(dp\),与\(dp\)过程本身息息相关的\(dp\) 动态dp 概述 一些简单的\(dp\)如果带修改怎么办 如果状态是普通设法,修改一个值的话包含这个值的所有情况都会被改,均摊\(O(n)\) 一种粗暴的看法,我们可以让所有状态包含的值均摊,比如用倍增划分区间的设法,让均摊被包含的状态数变为\(\log\),但这个说法很模糊,我们并不知道什么状态可以倍增 事实上,这牵扯到一个很重要的东西,"转移"这个运算有结合律否 如果有的话