[复习]动态dp

你还是可以认为我原来写的动态dp就是在扯蛋。

[Luogu4719]【模板】动态dp

首先作为一个\(dp\)题,我们显然可以每次修改之后都进行暴力\(dp\),设\(f[i][0/1]\)表示当前考虑\(i\)及其子树内的点,当前这个点是选还是不选时能够得到的最大权值,那么我们可以得到转移:\(f[i][0]+=\max\{f[v][0],f[v][1]\},f[i][1]+=f[v][0]\),其中\(v\)是\(i\)的一个儿子。

那么这样子的复杂度就是\(O(qn)\)。

仔细想想我们这样子为什么慢?因为我们重复计算了大量相同的转移,那么我们发现我们可以每次只需要修改当前的修改点到达根节点的这条链的所有点的\(dp\)值就好了,然而这样子复杂度最差还是\(O(nq)\)的。

那么我们还有哪里慢呢?

仔细想想,我们的转移都是一模一样的,区别只是在于每次的取值不同而已。

先考虑一模一样的转移可以怎么处理,似乎可以矩阵乘法?那么转移可以写成:

\[\begin{bmatrix}0&0\\V_u&-\infty\end{bmatrix}\times \begin{bmatrix}f_{v,0}\\f_{v,1}\end{bmatrix}=\begin{bmatrix}f_{u,0}\\f_{u,1}\end{bmatrix}
\]

然而这样子的转移只有在这个点转移第一个儿子的时候是对的。

如果有多个儿子的话,我们把\(V_u\)看成除了这个儿子之外的\(f[u][1]\),\(0\)看成除了这个儿子之外的\(f[u][0]\)。

那么等价于是我们要把儿子给分成两个部分,第一部分表示的是这个特殊转移的儿子,第二部分是剩下的儿子。似乎可以重链剖分?

那么对于重儿子我们可以直接维护这个矩阵,这样子用线段树维护矩阵乘法就可以算出重儿子部分的贡献,其他轻儿子暴力往上贡献。

看起来这个东西复杂度就很对了啊,单次修改只会造成\(log\)次暴力修改。

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAX 100100
#define lson (now<<1)
#define rson (now<<1|1)
const ll inf=1e17;
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,Q,V[MAX];
int fa[MAX],top[MAX],bot[MAX],size[MAX],hson[MAX],dfn[MAX],tim,ln[MAX];
void dfs1(int u,int ff)
{
fa[u]=ff;size[u]=1;
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;if(v==ff)continue;
dfs1(v,u);size[u]+=size[v];
if(size[v]>size[hson[u]])hson[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp;dfn[u]=++tim,ln[tim]=u;
if(hson[u])dfs2(hson[u],tp),bot[u]=bot[hson[u]];
else bot[u]=u;
for(int i=h[u];i;i=e[i].next)
if(e[i].v!=fa[u]&&e[i].v!=hson[u])
dfs2(e[i].v,e[i].v);
}
ll f[MAX][2];
void dfs(int u,int ff)
{
f[u][0]=0;f[u][1]=V[u];
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;if(v==ff)continue;
dfs(v,u);
f[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
}
}
struct Matrix
{
ll a[2][2];
ll*operator[](int x){return a[x];}
}t[MAX<<2],tmp[MAX];
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=h[u];i;i=e[i].next)
if(e[i].v!=fa[u]&&e[i].v!=hson[u])
g0+=max(f[e[i].v][0],f[e[i].v][1]),g1+=f[e[i].v][0];
tmp[l]=t[now]=(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 Modify(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()
{
n=read();Q=read();
for(int i=1;i<=n;++i)V[i]=read();
for(int i=1,u,v;i<n;++i)u=read(),v=read(),Add(u,v),Add(v,u);
dfs1(1,0);dfs2(1,1);dfs(1,0);Build(1,1,n);
while(Q--)
{
int x=read(),w=read();Modify(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;
}

[NOIP2018]保卫王国

要求最小点覆盖。这个东西等于权值和减去最大独立集。

而最大独立集就是上面的那个东西。

考虑如何强制某个点必须选或者不选,如果必须选则将其权值减去一个\(inf\),如果必须不选则将其权值加上一个\(inf\)。

然后就做完了。

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAX 100100
#define lson (now<<1)
#define rson (now<<1|1)
const ll inf=1e17;
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,Q;ll V[MAX];
int fa[MAX],top[MAX],bot[MAX],size[MAX],hson[MAX],dfn[MAX],tim,ln[MAX];
void dfs1(int u,int ff)
{
fa[u]=ff;size[u]=1;
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;if(v==ff)continue;
dfs1(v,u);size[u]+=size[v];
if(size[v]>size[hson[u]])hson[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp;dfn[u]=++tim,ln[tim]=u;
if(hson[u])dfs2(hson[u],tp),bot[u]=bot[hson[u]];
else bot[u]=u;
for(int i=h[u];i;i=e[i].next)
if(e[i].v!=fa[u]&&e[i].v!=hson[u])
dfs2(e[i].v,e[i].v);
}
ll f[MAX][2];
void dfs(int u,int ff)
{
f[u][0]=0;f[u][1]=V[u];
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;if(v==ff)continue;
dfs(v,u);
f[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
}
}
struct Matrix
{
ll a[2][2];
ll*operator[](int x){return a[x];}
}t[MAX<<2],tmp[MAX];
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=h[u];i;i=e[i].next)
if(e[i].v!=fa[u]&&e[i].v!=hson[u])
g0+=max(f[e[i].v][0],f[e[i].v][1]),g1+=f[e[i].v][0];
tmp[l]=t[now]=(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 Modify(int u,ll 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()
{
n=read();Q=read();scanf("%*s");ll sum=0;
for(int i=1;i<=n;++i)sum+=(V[i]=read());
for(int i=1,u,v;i<n;++i)u=read(),v=read(),Add(u,v),Add(v,u);
dfs1(1,0);dfs2(1,1);dfs(1,0);Build(1,1,n);
while(Q--)
{
int a=read(),x=read(),b=read(),y=read(),pa=V[a],pb=V[b];
ll pls=0;
if(!x)Modify(a,pa+inf),pls+=inf;else Modify(a,pa-inf);
if(!y)Modify(b,pb+inf),pls+=inf;else Modify(b,pb-inf);
Matrix ret=Query(1,1,n,dfn[1],dfn[bot[1]]);
ll ans=max(ret[0][0],ret[1][0])-pls;
if(ans<0)puts("-1");
else printf("%lld\n",sum-ans);
Modify(a,pa);Modify(b,pb);
}
return 0;
}

[SDOI2017]切树游戏

戳这里

[复习]动态dp的更多相关文章

  1. LOJ2269. 「SDOI2017」切树游戏 [FWT,动态DP]

    LOJ 思路 显然是要DP的.设\(dp_{u,i}\)表示\(u\)子树内一个包含\(u\)的连通块异或出\(i\)的方案数,发现转移可以用FWT优化,写成生成函数就是这样的: \[ dp_{u}= ...

  2. 动态DP之全局平衡二叉树

    目录 前置知识 全局平衡二叉树 大致介绍 建图过程 修改过程 询问过程 时间复杂度的证明 板题 前置知识 在学习如何使用全局平衡二叉树之前,你首先要知道如何使用树链剖分解决动态DP问题.这里仅做一个简 ...

  3. Luogu P4643 【模板】动态dp

    题目链接 Luogu P4643 题解 猫锟在WC2018讲的黑科技--动态DP,就是一个画风正常的DP问题再加上一个动态修改操作,就像这道题一样.(这道题也是PPT中的例题) 动态DP的一个套路是把 ...

  4. 动态dp学习笔记

    我们经常会遇到一些问题,是一些dp的模型,但是加上了什么待修改强制在线之类的,十分毒瘤,如果能有一个模式化的东西解决这类问题就会非常好. 给定一棵n个点的树,点带点权. 有m次操作,每次操作给定x,y ...

  5. 洛谷P4719 动态dp

    动态DP其实挺简单一个东西. 把DP值的定义改成去掉重儿子之后的DP值. 重链上的答案就用线段树/lct维护,维护子段/矩阵都可以.其实本质上差不多... 修改的时候在log个线段树上修改.轻儿子所在 ...

  6. 动态 DP 学习笔记

    不得不承认,去年提高组 D2T3 对动态 DP 起到了良好的普及效果. 动态 DP 主要用于解决一类问题.这类问题一般原本都是较为简单的树上 DP 问题,但是被套上了丧心病狂的修改点权的操作.举个例子 ...

  7. 动态dp初探

    动态dp初探 动态区间最大子段和问题 给出长度为\(n\)的序列和\(m\)次操作,每次修改一个元素的值或查询区间的最大字段和(SP1714 GSS3). 设\(f[i]\)为以下标\(i\)结尾的最 ...

  8. [总结] 动态DP学习笔记

    学习了一下动态DP 问题的来源: 给定一棵 \(n\) 个节点的树,点有点权,有 \(m\) 次修改单点点权的操作,回答每次操作之后的最大带权独立集大小. 首先一个显然的 \(O(nm)\) 的做法就 ...

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

    题目分析: 不难发现可以用动态DP做. 题目相当于是要我求一条路径,所有与路径有交的链的代价加入进去,要求代价最大. 我们把链的代价分成两个部分:一部分将代价加入$LCA$之中,用$g$数组保存:另一 ...

随机推荐

  1. 怎么去掉Xcode工程中的某种类型的警告 Implicit conversion loses integer precision: 'NSInteger' (aka 'long') to 'int32

    unsigned long numComponents = CGColorGetNumberOfComponents([[UIColor blackColor] CGColor]); 2014年12月 ...

  2. Java 学习使用常见的开源连接池

    目录 连接池介绍 自定义连接池 JDBC Tomcat Pool DBCP(DataBase Connection Pool) 使用配置文件来设置DBCP C3P0 Druid 连接池介绍 在说连接池 ...

  3. PostgreSQL 安装了contrib 之后 登录失败的问题

    1. 自己之前只是安装了 pg 10.6 2. 开发同事 需要用到 一个extensions 叫做 uuid-ossp 3. 执行报错  详情见昨天的blog 4. 然后执行了升级操作 结果 pg10 ...

  4. Laravel5.5+ 区分前后端用户登录

    Laravel 的用户认证是通过 Auth Facade 门脸实现的,手动认证可是使用  Auth::login() 或 Auth::attempt() 这两个方法实现. 以下内容纯属个人实现,也许有 ...

  5. Ionic常用命令

    安装ionic npm install -g ionic 更新www/lib/ionic 目录的文件,如有项目中有bower,此命令会运行bower update ionic, 否则则会从CDN上下载 ...

  6. ajax获取值的两种方法

    详细连接https://blog.csdn.net/a1102325298/article/details/80785143 ajax获得表单值的俩种方法 2018年06月23日 17:12:02 延 ...

  7. Druid数据库连接池

    一.Druid连接池的创建 package cn.zhouzhou; import java.io.IOException; import java.io.InputStream; import ja ...

  8. RabbitMQ在Ubuntu 16.04下的安装与配置

    安装执行如下命令: echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d ...

  9. Nginx简单的负载均衡入门

    nginx是用来管理tomcat的,只管理tomcat,并没有管理具体tomcat里面的项目,这里实现了简单的nginx管理两个tomcat的配置,注意upstream节点应该配置到service节点 ...

  10. 【Python练习题】程序3

    3.题目:一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?x+100 = n*nx+100+168 = m * m 所以(m+n)*(m-n) =168 #题 ...