题目描述

给定一棵n个点的树,点带点权。

有m次操作,每次操作给定x,y,表示修改点x的权值为y。

你需要在每次操作之后求出这棵树的最大权独立集的权值大小。

输入输出格式

输入格式:

第一行,n,m分别代表点数和操作数。

第二行,V1,V2,...,Vn,代表n个点的权值。

接下来n−1行,x,y,描述这棵树的n−1条边。

接下来m行,x,y,修改点x的权值为y。

输出格式:

对于每个操作输出一行一个整数,代表这次操作后的树上最大权独立集。

保证答案在int范围内

动态DP讲解:
 
考虑最大独立集转移方程:
$F[u][0]$ 表示 $u$ 点为根,不选$u$点的最大独立集.
$F[u][1]$ 表示 $u$ 点为根,选$u$点的最大独立集.
转移:
$F[u][0]=\sum_{v \in son[u]}max(F[u][0],F[u][1])$
$F[u][1]=\sum_{v \in son[u]}F[u][0]$
不难发现,每当我们修改一个点的点权,我们只会影响到该点到根节点这条链上的DP值.
如果树的高度是 $log(n)$,那么暴力跳链进行修改是完全可行的.
然而,题中树的高度可能会被恶意卡成 $O(n)$ 级别的.
那么,暴力跳链的总体复杂度就退化为 $O(nm)$ 了.
我们需要一个优秀的做法,使得每次修改都不受树高影响.
方法一 :  树链剖分 + 线段树 + 矩阵乘法.
注意的是,我们这里的矩阵乘法是长这样的:

Matrix operator*(Matrix a,Matrix b)
{
Matrix c;
c[0][0]=max(a[0][0]+b[0][0],a[0][1]+b[1][0]);
c[0][1]=max(a[0][0]+b[0][1],a[0][1]+b[1][1]);
c[1][0]=max(a[1][0]+b[0][0],a[1][1]+b[1][0]);
c[1][1]=max(a[1][0]+b[0][1],a[1][1]+b[1][1]);
return c;
}

我们用矩阵乘法维护每一次链上的转移.

我们用矩阵乘法维护每一次链上的转移.
根据常识,矩阵乘法要求满足结合律,而每一次状态转移也是满足结合律的.
对于每一个点开一个矩阵 $M_{u}$.
 $M_{u}=\begin{bmatrix} F_{u,0} & F_{u,0}\\ F_{u,1}  & -\infty \end{bmatrix}$
 考虑已经得到一个点 $i$ 的 $F_{i,0}$ 及 $F_{i,1}$,以及 $fa_{i}$ 的 $M_{fa_{i}}$.
 
 我们就可以通过矩阵乘法转移到 $fa_{i}$ 的方程了 !
 设 $u=fa_{i}$
 即 $\begin{bmatrix} F_{u,0} & F_{u,0}\\ F_{u,1}  & -\infty \end{bmatrix}\times\begin{bmatrix} F_{i,0}\\F_{i,1} \end{bmatrix}=\begin{bmatrix} F_{u,0}\\F_{u,1} \end{bmatrix}$
 是不是十分巧妙 ? (注:矩阵中的 $F[u][0]$ 与 $F[u][1]$ 指的是不包括该点重儿子的DP值,$i$ 是该点的重儿子,我们后文会提及)
 优秀的是,这些矩阵转移是由线段树来维护的.
 准确来说,重链上的线段树.
 我们引入树链剖分.
  
 
 
 
    
 
 
 
 考虑树剖的原理:
树剖将树剖成不超过 $log(n)$ 个重链,而树上任意两点最终都会蹦到同一个重链上.
 最坏也只会跳 $log(n)$ 次链顶,所以时间复杂度是十分优秀的. 
 而在动态 DP 中,我们也是用蹦链的方式来实现.
 即每条重链用一颗线段树维护,线段树来维护一段区间的矩阵乘法.
 这样,每次修改与查询就在对应的线段树中进行.
 
考虑修改:
在上文中,我们已经知道,每次修改只会影响到该点到根节点的一条路径,我们只需修改该点到根节点这条链上的信息即可.
现在在一条重链上的线段树进行单点修改,这是很简单的.
然后,我们再跳到该点所属重链的父亲上(设为 $fa$)
由于修改的是轻儿子,所以 $fa$ 的 $F[u][0]$ 与 $F[u][1]$ 都需要改变.
 
考虑查询:
由于我们在修改时已经将所有信息全部修改完毕,只需调用出根节点所在的重链的线段树, 并查一下区间矩阵乘法的结果即可.
 

Code:

#include<bits/stdc++.h>
#define setIO(s) freopen(s".in","r",stdin)
#define maxn 300000
#define lson (now<<1)
#define rson ((now<<1)|1)
#define ll long long
const ll inf = 1e17;
using namespace std;
int hd[maxn],to[maxn],nex[maxn],V[maxn],hson[maxn],fa[maxn],dfn[maxn], ln[maxn],F[maxn][2],siz[maxn],top[maxn],bot[maxn];
int edges,tim,n,Q;
void add(int u,int v)
{
nex[++edges]=hd[u],hd[u]=edges,to[edges]=v;
}
void dfs1(int u,int ff)
{
siz[u]=1,fa[u]=ff;
for(int i=hd[u];i;i=nex[i])
{
if(to[i]==ff) continue;
dfs1(to[i],u);
siz[u]+=siz[to[i]];
if(siz[to[i]] > siz[hson[u]]) hson[u]=to[i];
}
}
void dfs2(int u,int tp)
{
top[u]=tp,ln[++tim]=u,dfn[u]=tim;
if(hson[u])
dfs2(hson[u], tp), bot[u]=bot[hson[u]];
else
bot[u]=u;
for(int i=hd[u];i;i=nex[i])
{
if(to[i]==fa[u]||to[i]==hson[u]) continue;
dfs2(to[i],to[i]);
}
}
void dfs(int u)
{
F[u][0]=0,F[u][1]=V[u];
for(int i=hd[u];i;i=nex[i])
{
if(to[i]==fa[u]) continue;
dfs(to[i]);
F[u][0]+=max(F[to[i]][1],F[to[i]][0]);
F[u][1]+=F[to[i]][0];
}
}
struct Matrix
{
ll a[2][2];
ll*operator[](int x){ return a[x]; }
}t[maxn<<1],tmp[maxn<<1];
Matrix operator*(Matrix a,Matrix b)
{
Matrix c;
c[0][0]=max(a[0][0]+b[0][0],a[0][1]+b[1][0]);
c[0][1]=max(a[0][0]+b[0][1],a[0][1]+b[1][1]);
c[1][0]=max(a[1][0]+b[0][0],a[1][1]+b[1][0]);
c[1][1]=max(a[1][0]+b[0][1],a[1][1]+b[1][1]);
return c;
}
void Build(int now,int l,int r)
{
if(l==r)
{
int u=ln[l];
ll g0=0,g1=V[u];
for(int i=hd[u];i;i=nex[i])
{
int v=to[i];
if(v==fa[u]||v==hson[u]) continue;
g0+=max(F[v][0],F[v][1]);
g1+=F[v][0];
}
t[now]=tmp[l]=(Matrix) {g0,g0,g1,-inf};
return;
}
int mid=(l+r)>>1;
Build(lson,l,mid),Build(rson,mid+1,r);
t[now]=t[lson]*t[rson];
}
void Modify(int now,int l,int r,int p)
{
if(l==r)
{
t[now]=tmp[l];
return;
}
int mid=(l+r)>>1;
if(p<=mid) Modify(lson,l,mid,p);
else Modify(rson,mid+1,r,p);
t[now]=t[lson]*t[rson];
}
Matrix Query(int now,int l,int r,int L,int R)
{
if(L==l&&r==R) return t[now];
int mid=(l+r)>>1;
if(R<=mid) return Query(lson,l,mid,L,R);
if(L>mid) return Query(rson,mid+1,r,L,R);
return Query(lson,l,mid,L,mid)*Query(rson,mid+1,r,mid+1,R);
}
void Update(int u,int w)
{
tmp[dfn[u]][1][0]+=w-V[u],V[u]=w;
while(u)
{
Matrix a=Query(1,1,n,dfn[top[u]],dfn[bot[u]]);
Modify(1,1,n,dfn[u]);
Matrix b=Query(1,1,n,dfn[top[u]],dfn[bot[u]]);
u=fa[top[u]];
if(!u) break;
int x = dfn[u];
ll g0=a[0][0],g1=a[1][0],f0=b[0][0],f1=b[1][0];
tmp[x][0][0]=tmp[x][0][1]=tmp[x][0][0]+max(f0,f1)-max(g0,g1);
tmp[x][1][0]=tmp[x][1][0]+f0-g0;
}
}
int main()
{
// setIO("input");
scanf("%d%d",&n,&Q);
for(int i=1;i<=n;++i) scanf("%d",&V[i]);
for(int i=1,u,v;i<n;++i)
{
scanf("%d%d",&u,&v),add(u,v),add(v,u);
}
dfs1(1,0),dfs2(1,1),dfs(1),Build(1,1,n);
while(Q--)
{
int x,w;
scanf("%d%d",&x,&w);
Update(x,w);
Matrix ans=Query(1,1,n,dfn[1],dfn[bot[1]]);
printf("%lld\n",max(ans[0][0],ans[1][0]));
}
return 0;
}

  

luoguP4719 【模板】动态 DP 线段树+树链剖分+矩阵乘法+动态DP的更多相关文章

  1. 51nod 1462 树据结构 | 树链剖分 矩阵乘法

    题目链接 51nod 1462 题目描述 给一颗以1为根的树. 每个点有两个权值:vi, ti,一开始全部是零. Q次操作: 读入o, u, d o = 1 对u到根上所有点的vi += d o = ...

  2. 形态形成场(矩阵乘法优化dp)

    形态形成场(矩阵乘法优化dp) 短信中将会涉及前\(k\)种大写字母,每个大写字母都有一个对应的替换式\(Si\),替换式中只会出现大写字母和数字,比如\(A→BB,B→CC0,C→123\),代表 ...

  3. 【bzoj1778】[Usaco2010 Hol]Dotp 驱逐猪猡 矩阵乘法+概率dp+高斯消元

    题目描述 奶牛们建立了一个随机化的臭气炸弹来驱逐猪猡.猪猡的文明包含1到N (2 <= N <= 300)一共N个猪城.这些城市由M (1 <= M <= 44,850)条由两 ...

  4. 斐波那契数列 矩阵乘法优化DP

    斐波那契数列 矩阵乘法优化DP 求\(f(n) \%1000000007​\),\(n\le 10^{18}​\) 矩阵乘法:\(i\times k\)的矩阵\(A\)乘\(k\times j\)的矩 ...

  5. 线段树&数链剖分

    傻逼线段树,傻逼数剖 线段树 定义: 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 使用线段树可以快速的查找某一个节点在若干条线段中出现 ...

  6. 【清澄A1333】【整体二分+二维树状数组】矩阵乘法(梁盾)

    试题来源 2012中国国家集训队命题答辩 问题描述 给你一个N*N的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第K小数. 输入格式 第一行两个数N,Q,表示矩阵大小和询问组数: 接下来N行N列一共 ...

  7. 矩阵乘法优化DP复习

    前言 最近做毒瘤做多了--联赛难度的东西也该复习复习了. Warning:本文较长,难度分界线在"中场休息"部分,如果只想看普及难度的可以从第五部分直接到注意事项qwq 文中用(比 ...

  8. [BZOJ 1009] [HNOI2008] GT考试 【AC自动机 + 矩阵乘法优化DP】

    题目链接:BZOJ - 1009 题目分析 题目要求求出不包含给定字符串的长度为 n 的字符串的数量. 既然这样,应该就是 KMP + DP ,用 f[i][j] 表示长度为 i ,匹配到模式串第 j ...

  9. 矩阵乘法优化DP

    本文讲一下一些基本的矩阵优化DP的方法技巧. 定义三个矩阵A,B,C,其中行和列分别为$m\times n,n \times p,m\times p$,(其中行是从上往下数的,列是从左往右数的) $C ...

随机推荐

  1. 关于linux中使用vim打开文件出现^M的解决方法

    在linux下,不可避免的会用VIM打开一些windows下编辑过的文本文件.我们会发现文件的每行结尾都会有一个^M符号,这是因为 DOS下的编辑器和Linux编辑器对文件行末的回车符处理不一致, 各 ...

  2. CF #319 div 2 D

    这道题算不算脑洞题.. 可以发现,当一个排列中有循环节时长度为1或2时可能有解.当为1时,只需把全部点都连到这个题即可,当为2时,就要求所有循环节长度均为偶数,这很容易理解,因为如果存在为奇数,它们之 ...

  3. powerdesigner 16.5 视图的显示

    今天中午为了解说绘图.然后把toolbox跟object brower都关掉了,花了1个小时才找到toolbox 1.object brower显示 直接使用快捷键 ALT+0 2.toolbox(也 ...

  4. JSONObject.toBean() data转换

    //toBean方法不识别date属性  要把VO的DATE该成UTIL包下的DATE 然后加上下面的代码.不然会报错JSONUtils.getMorpherRegistry().registerMo ...

  5. linux下获取按键响应事件【转】

    本文转载自:https://my.oschina.net/u/157503/blog/91548 1.问题 通过一个死循环将读取键盘对应的设备文件将触发键盘事件在屏幕上打印出来,按esc退出程序 代码 ...

  6. bzoj5130: [Lydsy1712月赛]字符串的周期

    这道题很有意思啊. 字符串循环节用KMP(手推一下) 假如是26^12肯定很不滋磁 但是可以发现ABA和BCB和BAB这些都是等价的 那就把最小的拿出来搞再乘个排列数就好了 #include<c ...

  7. C# Path 有关于文件路径等问题类(转)

    C# Path 标签:C#, Path C-Sharp  0 Path handles file path processing. The .NET Framework provides effect ...

  8. Your configuration specifies to merge with the ref 'refs/heads/v.autoCheckProduct.20190325' from the remote, but no such ref was fetched.

    问题: 创建新的分支,当我们执行git pull,出现如下错误 解决办法: 1.切换到主分支(或者被依赖的分支,也就是你从哪个分支上拉取新的分支),博主这里是master分支 2.执行以下两个命令 3 ...

  9. MSSQL:删除系统作业计划

    use [msdb]declare @job_name varchar(100)set @job_name = N'EveryDayBackup.Subplan_1'--注:jobName为维护计划对 ...

  10. UIView动画基础

    1 Position 平移 [UIView animateWithDuration:1.0 animations:^{ _blueView.centerX = self.view.width -100 ...