必备知识

​ 树链剖分,最大权独立集(即没有上司的舞会(树上DP)),矩阵乘法;

D-DP

模版简述

模板

​ 关于动态DP,其实是关于一类动态修改点权的问题,但是很难去处理;

​ 我们平常的DP经常是离线DP,而当在线时,就会出现事故;

​ D-DP是关于求最大权独立集的,支持动态修改点值,其思想即DP的通项递推式改为矩阵乘的形式进行递推,而树上问题就可以使用树链剖分提前处理出区间矩阵乘;

​ 大家肯定都会 \(O(nm)\) 做法,即暴力修改点值进行DP.

​ 求最大权独立集的DP式子为 \(f[x][1]=\sum f[y][0] , f[x][0]=\sum max(f[y][1],f[y][0])\) ,如果我们将其改成矩阵乘的形式,就变成了下面这样;

\[[^{f[y][0]}_{f[y][1]}] * [^{0,0}_{v[x],-inf}]=[^{f[x][0]}_{f[x][1]}]
\]

​ 这个式子并不完整,这只是从一个点的矩阵乘,这个式子只是类似于矩阵乘,我们可以重新定义一下矩阵乘的定义,即将加法改为 \(max\) ,将乘法改为加法,那么就可以通项递推了;

​ 但是这个式子只限于从一个点递推过来,那么其他点怎么办?

​ 没有办法...但是这是一个思路,这个式子用线段树维护,可以让我们可以快速处理出链上信息.

​ 既然在树上,我们不如将重链看成这个链,而将链上的轻儿子的信息提前处理出来,在重链上的点的信息通过进行矩阵速推.

\[[^{f[y][0]}_{f[y][1]}]*[^{f'[x][0],f'[x][0]}_{f'[x][1],-inf}]=[^{f[x][0]}_{f[x][1]}]
\]

​ (注: \(f'[x][0]\) 是点 \(x\) 只处理轻儿子和自己信息时的值)

​ 那么如何修改?

​ 同一条重链上点的修改对于这条链上的点是没有影响的,因为重链每个点只保存其轻儿子的信息,有影响的是对于这条链 \(top\) 的父亲节点,那么我们的思路就出来了,修改点时,修改链 \(top\) 父亲的矩阵信息即可;

​ 总结一下思路: 首先将DP式子化为通项矩阵乘的形式,先处理出每个节点轻儿子的信息,在树上用树链剖分维护链上矩阵乘信息,修改时,每次修改链首父亲的矩阵值,修改至树根重链.修改细节即修改点信息,之后从链尾到链首矩阵乘,与之前的值作差修改.最后求答案时直接处理链上信息即可;

code
#include<bits/stdc++.h>
#define le(p) p<<1
#define re(p) p<<1|1
#define ll long long
using namespace std;
const int maxn=1e5+7,inf=1e9;
int n,m,head[maxn],son[maxn],top[maxn],bot[maxn],fl[maxn];
int fa[maxn],id[maxn],cent,tot,a[maxn],sz[maxn],f[maxn][2];
struct node{
int next,to;
}edge[maxn<<1];
struct matrix{
ll g[2][2];
matrix operator *(const matrix x) const{
matrix ans;
ans.g[0][0]=max(g[0][0]+x.g[0][0],g[0][1]+x.g[1][0]);
ans.g[0][1]=max(g[0][0]+x.g[0][1],g[0][1]+x.g[1][1]);
ans.g[1][0]=max(g[1][0]+x.g[0][0],g[1][1]+x.g[1][0]);
ans.g[1][1]=max(g[1][0]+x.g[0][1],g[1][1]+x.g[1][1]);
return ans;
}//矩阵乘
}tr[maxn<<2];
matrix ori[maxn]; template<typename type_of_scan>
inline void scan(type_of_scan &x){
type_of_scan f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
template<typename top,typename... tops>
inline void scan(top &x,tops&... X){
scan(x),scan(X...);
} inline void add(int u,int v){
edge[++cent]=(node){head[u],v};head[u]=cent;
edge[++cent]=(node){head[v],u};head[v]=cent;
}
//树链剖分预处理
void dfs1(int x){
sz[x]=1;
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa[x]) continue;
fa[y]=x,dfs1(y);
sz[x]+=sz[y];
if(sz[y]>sz[son[x]]) son[x]=y;
}
} void dfs2(int x,int tp){
id[x]=++tot,fl[tot]=x;top[x]=tp;
if(son[x]) dfs2(son[x],tp),bot[x]=bot[son[x]];
else return bot[x]=x,void();
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa[x]||y==son[x]) continue;
dfs2(y,y);
}
}
//树上DP
void dfs3(int x){
f[x][0]=0;f[x][1]=a[x];
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa[x]) continue;
dfs3(y);
f[x][0]+=max(f[y][0],f[y][1]);
f[x][1]+=f[y][0];
}
} void push_up(int p){
tr[p]=tr[le(p)]*tr[re(p)];
}
//建树时每个点只保存自己和轻儿子的信息
void build(int l,int r,int p){
if(l==r){
tr[p].g[0][0]=tr[p].g[0][1]=0;tr[p].g[1][1]=-inf;
tr[p].g[1][0]=a[fl[l]];int x=fl[l];
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa[x]||y==son[x]) continue;
tr[p].g[0][0]+=max(f[y][0],f[y][1]);
tr[p].g[1][0]+=f[y][0];
}
tr[p].g[0][1]=tr[p].g[0][0];
ori[fl[l]]=tr[p];
return ;
}
int mid=l+r>>1;
build(l,mid,le(p));build(mid+1,r,re(p));
push_up(p);
} void add(int x,int l,int r,int p){
if(l==r) return tr[p]=ori[fl[l]],void();
int mid=l+r>>1;
if(x<=mid) add(x,l,mid,le(p));
else add(x,mid+1,r,re(p));
push_up(p);
} matrix query(int nl,int nr,int l,int r,int p){
if(nl<=l&&r<=nr) return tr[p];
int mid=l+r>>1;
if(nl<=mid&&mid<nr) return query(nl,nr,l,mid,le(p))*query(nl,nr,mid+1,r,re(p));
else if(nl<=mid) return query(nl,nr,l,mid,le(p));
else return query(nl,nr,mid+1,r,re(p));
} matrix query(int x){
return query(id[top[x]],id[bot[x]],1,n,1);
} matrix solve(int x,int k){
ori[x].g[1][0]+=k-a[x];a[x]=k;
while(x){
matrix old,news;
old=query(top[x]);//没修改的
add(id[x],1,n,1);//change
news=query(top[x]);//修改过的
x=fa[top[x]];if(!x) break;
ori[x].g[0][0]+=max(news.g[0][0],news.g[1][0])-max(old.g[0][0],old.g[1][0]);
ori[x].g[0][1]=ori[x].g[0][0];
ori[x].g[1][0]+=news.g[0][0]-old.g[0][0];
}
return query(1);
} int main(){
scan(n,m);
for(int i=1;i<=n;i++) scan(a[i]);
for(int i=1,u,v;i<=n-1;i++)
scan(u),scan(v),add(u,v);
dfs1(1),dfs2(1,1),dfs3(1);build(1,n,1);
while(m--){
int x,k;
scan(x,k);
matrix ans=solve(x,k);
printf("%d\n",max(ans.g[0][0],ans.g[1][0]));
}
return 0;
}

例题

​ [NOIP2018]保卫王国;

​ 有两个点,强制选点或强制不选,求最小覆盖集;

​ 最小覆盖集=全集-最大权独立集;

​ 强制选点或不选可以通过将其改为 \(-inf\) 和 \(inf\) ,之后就是D-DP模板了;

code
#include<bits/stdc++.h>
#define ll long long
#define le(x) x<<1
#define re(x) x<<1|1
using namespace std;
const int maxn=1e5+7;
const ll inf=1e9;
int n,m,fa[maxn],id[maxn],fl[maxn],sz[maxn],head[maxn];
int son[maxn],top[maxn],cent,tot,bot[maxn];
ll f[maxn][2],a[maxn],sum,ans;
vector<int>to[maxn];
char type[2];
struct node{
int next,to;
}edge[maxn<<1];
struct matrix{
ll g[2][2];
matrix operator *(const matrix x) const{
matrix ans;
ans.g[0][0]=max(g[0][0]+x.g[0][0],g[0][1]+x.g[1][0]);
ans.g[0][1]=max(g[0][0]+x.g[0][1],g[0][1]+x.g[1][1]);
ans.g[1][0]=max(g[1][0]+x.g[0][0],g[1][1]+x.g[1][0]);
ans.g[1][1]=max(g[1][0]+x.g[0][1],g[1][1]+x.g[1][1]);
return ans;
}
}tr[maxn<<3];
matrix ori[maxn]; template<typename type_of_scan>
inline void scan(type_of_scan &x){
type_of_scan f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
template<typename top,typename... tops>
inline void scan(top &x,tops&... X){
scan(x),scan(X...);
} inline void add(int u,int v){
edge[++cent]=(node){head[u],v};head[u]=cent;
edge[++cent]=(node){head[v],u};head[v]=cent;
to[u].push_back(v),to[v].push_back(u);
} void dfs1(int x){
sz[x]=1;
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa[x]) continue;
fa[y]=x;dfs1(y);
sz[x]+=sz[y];
if(sz[y]>sz[son[x]]) son[x]=y;
}
} void dfs2(int x,int tp){
id[x]=++tot,fl[tot]=x,top[x]=tp;
if(son[x]) dfs2(son[x],tp),bot[x]=bot[son[x]];
else return bot[x]=x,void();
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa[x]||y==son[x]) continue;
dfs2(y,y);
}
} void dfs3(int x){
f[x][1]=a[x],f[x][0]=0;
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa[x]) continue;
dfs3(y);
f[x][0]+=max(f[y][0],f[y][1]);
f[x][1]+=f[y][0];
}
} void push_up(int p){
tr[p]=tr[le(p)]*tr[re(p)];
} void build(int l,int r,int p){
if(l==r){
tr[p].g[0][0]=tr[p].g[0][1]=0;int x=fl[l];
tr[p].g[1][0]=a[fl[l]];tr[p].g[1][1]=-inf;
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa[x]||y==son[x]) continue;
tr[p].g[0][0]+=max(f[y][0],f[y][1]);
tr[p].g[1][0]+=f[y][0];
}
tr[p].g[0][1]=tr[p].g[0][0];
ori[fl[l]]=tr[p];
return ;
}
int mid=l+r>>1;
build(l,mid,le(p)),build(mid+1,r,re(p));
push_up(p);
} void add(int x,int l,int r,int p){
if(l==r) return tr[p]=ori[fl[l]],void();
int mid=l+r>>1;
if(x<=mid) add(x,l,mid,le(p));
else add(x,mid+1,r,re(p));
push_up(p);
} matrix query(int nl,int nr,int l,int r,int p){
if(nl<=l&&r<=nr) return tr[p];
int mid=l+r>>1;
if(nl<=mid&&mid<nr) return query(nl,nr,l,mid,le(p))*query(nl,nr,mid+1,r,re(p));
else if(nl<=mid) return query(nl,nr,l,mid,le(p));
else return query(nl,nr,mid+1,r,re(p));
} matrix query(int x){
return query(id[top[x]],id[bot[x]],1,n,1);
} void modify(int x,ll k){
ori[x].g[1][0]+=k-a[x];a[x]=k;
while(x){
matrix old,news;
old=query(top[x]);add(id[x],1,n,1);
news=query(top[x]);
x=fa[top[x]];if(!x) break;
ori[x].g[0][0]+=max(news.g[0][0],news.g[1][0])-max(old.g[0][0],old.g[1][0]);
ori[x].g[0][1]=ori[x].g[0][0];
ori[x].g[1][0]+=news.g[0][0]-old.g[0][0];
}
return ;
} int main(){
scan(n,m);cin>>type;
for(int i=1;i<=n;i++) scan(a[i]),sum+=a[i];
for(int i=1,u,v;i<=n-1;i++)
scan(u),scan(v),add(u,v);
for(int i=1;i<=n;i++) sort(to[i].begin(),to[i].end());
dfs1(1),dfs2(1,1),dfs3(1),build(1,n,1);
while(m--){
int f1,f2,g1,g2,t1,t2;
scan(f1,f2,g1,g2);
if(f2==g2&&!f2){
vector<int>::iterator pos=lower_bound(to[f1].begin(),to[f1].end(),g1);
if(pos!=to[f1].end()&&(*pos)==g1){
puts("-1");continue;
}
}
t1=a[f1],t2=a[g1];
modify(f1,f2?-inf:inf);
modify(g1,g2?-inf:inf);
matrix ol=query(1);
ans=max(ol.g[0][0],ol.g[1][0]);
if(!f2) ans=ans-inf+t1;
if(!g2) ans=ans-inf+t2;
printf("%lld\n",sum-ans);
modify(f1,t1),modify(g1,t2);
}
return 0;
}

​ 当然还有倍增做法,但这里不再讲述,D-DP中一个重要思想就是讲DP式子变成矩阵乘的形式,用数据结构进行维护,其高效并支持拓展;

D-DP的更多相关文章

  1. BZOJ 1911: [Apio2010]特别行动队 [斜率优化DP]

    1911: [Apio2010]特别行动队 Time Limit: 4 Sec  Memory Limit: 64 MBSubmit: 4142  Solved: 1964[Submit][Statu ...

  2. 2013 Asia Changsha Regional Contest---Josephina and RPG(DP)

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=4800 Problem Description A role-playing game (RPG and ...

  3. AEAI DP V3.7.0 发布,开源综合应用开发平台

    1  升级说明 AEAI DP 3.7版本是AEAI DP一个里程碑版本,基于JDK1.7开发,在本版本中新增支持Rest服务开发机制(默认支持WebService服务开发机制),且支持WS服务.RS ...

  4. AEAI DP V3.6.0 升级说明,开源综合应用开发平台

    AEAI DP综合应用开发平台是一款扩展开发工具,专门用于开发MIS类的Java Web应用,本次发版的AEAI DP_v3.6.0版本为AEAI DP _v3.5.0版本的升级版本,该产品现已开源并 ...

  5. BZOJ 1597: [Usaco2008 Mar]土地购买 [斜率优化DP]

    1597: [Usaco2008 Mar]土地购买 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 4026  Solved: 1473[Submit] ...

  6. [斜率优化DP]【学习笔记】【更新中】

    参考资料: 1.元旦集训的课件已经很好了 http://files.cnblogs.com/files/candy99/dp.pdf 2.http://www.cnblogs.com/MashiroS ...

  7. BZOJ 1010: [HNOI2008]玩具装箱toy [DP 斜率优化]

    1010: [HNOI2008]玩具装箱toy Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 9812  Solved: 3978[Submit][St ...

  8. px、dp和sp,这些单位有什么区别?

    DP 这个是最常用但也最难理解的尺寸单位.它与“像素密度”密切相关,所以 首先我们解释一下什么是像素密度.假设有一部手机,屏幕的物理尺寸为1.5英寸x2英寸,屏幕分辨率为240x320,则我们可以计算 ...

  9. android px转换为dip/dp

    /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) */ public int dipTopx(Context context, float dpValue) { final floa ...

  10. POJ 3254. Corn Fields 状态压缩DP (入门级)

    Corn Fields Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 9806   Accepted: 5185 Descr ...

随机推荐

  1. 【基础】1001_Hello,World!

    题目相关 [题目描述] 编写一个能够输出"Hello,World!"的程序,这个程序常常作为一个初学者接触一门新的编程语言所写的第一个程序,也经常用来测试开发.编译环境是否能够正常 ...

  2. web元素定位和appium-app元素定位

    一.web页面元素定位工具介绍 1.打开google浏览器,按F12进入开发者模式,如下图: 2.用鼠标点击下图红色框中的箭头--然后鼠标移动到web页面的元素上(此处为百度框),会自动定位到对应的h ...

  3. Go语言从入门到放弃(设置 go get 为国内源)

    前言 Go语言学到 Gin 框架了, 其实每天学习是比较辛苦的事情, 坚持下去! 在使用 Go 过程中发现, 最无奈的是Go的一些模块下不下来, 即便挂了V, 油管2k不卡的那种, 依旧是 time ...

  4. 【SpringBoot1.x】SpringBoot1.x 安全

    SpringBoot1.x 安全 文章源码 环境搭建 SpringSecurity 是针对 Spring 项目的安全框架,也是 SpringBoot 底层安全模块默认的技术选型.他可以实现强大的 we ...

  5. 【MyBatis】MyBatis 延迟加载策略

    MyBatis 延迟加载策略 文章源码 什么是延迟加载 延迟加载,就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据,也被成为懒加载. 好处:先从单表查询,需要时再从关联表去关联查询,大大提 ...

  6. linux常用命令--转载

    转载自: https://www.cnblogs.com/Qsunshine/p/10402179.html 常用指令 ls 显示文件或目录 -l列出文件详细信息l(list) -a列出当前目录下所有 ...

  7. 【Spring】Spring的数据库开发 - 2、Spring JdbcTemplate的常用方法(execute、update、query)

    Spring JdbcTemplate的常用方法 文章目录 Spring JdbcTemplate的常用方法 execute() update() query() 简单记录-Java EE企业级应用开 ...

  8. kubernets之pod简介

    一  k8s集群里面的最小单位是pod 1.1 一个较为简单的pod的配置文件 apiverson: api的版本号 kind: 资源的种类 metadata: pod的种类等相关信息 spec: p ...

  9. IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that a host key has just been changed. The fingerp

    [root@k8s-master ~]# scp /etc/sysctl.d/k8s.conf root@192.168.30.23:/etc/sysctl.d/k8s.conf@@@@@@@@@@@ ...

  10. go语言循环变量

    阅读go语言圣经第五章第六节介绍到了捕获迭代变量 package main import ( "fmt" ) func main() { var lis []func() for ...