首页 > 其他分享 >动态DP

动态DP

时间:2023-07-13 21:13:37浏览次数:40  
标签:typedef int rep long 动态 DP define

题目链接

题目大意:动态维护树上最大权独立集。

所谓动态 DP,就是把原先能用 DP 解决的问题变成动态维护 DP 值。如果 DP 数组可以支持合并两条链的话,可以直接用线段树维护,否则需要考虑把 DP 的转移改成矩阵,这样就可以用线段树维护矩阵,不过支持合并链以及维护矩阵都需要树链剖分,所以复杂度至少是两个 $\log^2n$。

对于这个题,显然 DP 数组并不满足链合并的性质,所以我们考虑如何采用矩阵转移,设 $f_{i,0/1}$ 表示以 $i$ 为根子树是否选 $i$ 的最大权值和,$g_{i,0/1}$ 表示以 $i$ 为根,仅考虑轻儿子是否选 $i$ 的最大权值和,设 $son_i$ 表示 $i$ 的重儿子。那么有:

$$
\begin{pmatrix}
g_{i,0}&g_{i,1}\
g_{i,1}&-\infty
\end{pmatrix}
\begin{pmatrix}
f_{son_i,0}\
f_{son_i,1}
\end

\begin{pmatrix}
f_{i,0}\
f_{i,1}
\end{pmatrix}
$$

注意这里的矩阵乘法是在 $(+,\max)$ 下定义的。

众所周知,矩阵乘法只需要满足 $(\times,+)$ 满足结合率,交换律,而且 $\times$ 对 $+$ 有分配率。

这样,对于一次修改,设修改的点为 $u$,那么只需要修改 $g_{u,1}$ 的值,以及从 $u$ 往上到根节点所有经过的轻边父亲节点的 $g$,这些 $g$ 只需要求出重链顶端的 $f$ 值,暴力修改即可。

这样我们需要维护树上矩阵乘法,单点修改。

代码:

#include<bits/stdc++.h>
#define mset(a,b) memset((a),(b),sizeof((a)))
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define dec(i,l,r) for(int i=(r);i>=(l);i--)
#define cmax(a,b) (((a)<(b))?(a=b):(a))
#define cmin(a,b) (((a)>(b))?(a=b):(a))
#define Next(k) for(int x=head[k];x;x=li[x].next)
#define vc vector
#define ar array
#define pi pair
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define N 100010
#define M number
using namespace std;

typedef double dd;
typedef long double ld;
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
//#define int long long
typedef pair<int,int> P;
typedef vector<int> vi;

const int INF=0x3f3f3f3f;
const dd eps=1e-9;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

struct Matrix{
    int n,m,a[2][2];
    inline void Clear(){
        mset(a,0);n=m=0;
    }
    inline void Print(){
        printf("%d %d\n",n,m);
        rep(i,0,n-1){
            rep(j,0,m-1) printf("%d ",a[i][j]);puts("");
        }
    }
    inline Matrix operator * (const Matrix &b)const{
        // printf("n=%d m=%d b.n=%d b.m=%d\n",n,m,b.n,b.m);
        Matrix c;c.Clear();assert(m==b.n);c.n=n;c.m=b.m;
        rep(i,0,c.n-1)rep(j,0,c.m-1){
            c.a[i][j]=-INF;
            rep(k,0,m-1){
                cmax(c.a[i][j],a[i][k]+b.a[k][j]);
            }
        }
        return c;
    }
    inline void GetI(int len){
        (*this).Clear();n=m=len;
        rep(i,0,n-1)rep(j,0,m-1) a[i][j]=-INF;
        rep(i,0,len-1) a[i][i]=0;
    }
};
int en[N],top[N],siz[N],dep[N],id[N],rk[N],tot,fa[N],son[N],n,m,g[N][2],f[N][2],a[N];
vi e[N];
Matrix ze;
struct SegmentTree{
    #define ls(k) k<<1
    #define rs(k) k<<1|1
    Matrix a[N<<2];
    inline void PushUp(int k){
        a[k]=a[ls(k)]*a[rs(k)];
    }
    inline void Change(int k,int l,int r,int w,int i,int j,int x){
        if(l==r){
            a[k].a[i][j]+=x;return;
        }int mid=(l+r)>>1;
        if(w<=mid) Change(ls(k),l,mid,w,i,j,x);
        else Change(rs(k),mid+1,r,w,i,j,x);PushUp(k);
    }
    inline Matrix Ask(int k,int l,int r,int z,int y){
        if(z<=l&&r<=y){
            return a[k];
        }int mid=(l+r)>>1;Matrix now;now.GetI(2);
        if(z<=mid) now=now*Ask(ls(k),l,mid,z,y);
        if(mid<y) now=now*Ask(rs(k),mid+1,r,z,y);
        return now;
    }
    inline void Build(int k,int l,int r){
        if(l==r){
            int i=rk[l];
            a[k].a[0][0]=g[i][0];a[k].a[0][1]=g[i][0];a[k].a[1][0]=g[i][1];a[k].a[1][1]=-INF;a[k].m=a[k].n=2;
            return;
        }int mid=(l+r)>>1;Build(ls(k),l,mid);Build(rs(k),mid+1,r);PushUp(k);
    }
    inline void dfs(int k,int l,int r){
        int mid=(l+r)>>1;
        printf("k=%d\n",k);a[k].Print();if(l==r) return;
        dfs(ls(k),l,mid);dfs(rs(k),mid+1,r);
    }
}st;

inline void dfs(int k,int fat){
    fa[k]=fat;siz[k]=1;dep[k]=dep[fat]+1;for(int to:e[k])if(to!=fat){dfs(to,k);siz[k]+=siz[to];if(siz[son[k]]<siz[to]) son[k]=to;}
}
inline int Dfs(int k,int t){
    top[k]=t;id[k]=++tot;rk[tot]=k;int fia=-1;if(son[k])fia=Dfs(son[k],t);for(int to:e[k])if(to!=son[k]&&to!=fa[k]){Dfs(to,to);}if(fia==-1) fia=k;
    en[k]=fia;return en[k];
}
inline void DFs(int k){
    f[k][1]=a[k];f[k][0]=0;g[k][1]=a[k];g[k][0]=0;
    for(int to:e[k])if(to!=fa[k]) DFs(to);
    for(int to:e[k])if(to!=fa[k]){
        f[k][1]+=f[to][0];f[k][0]+=max(f[to][0],f[to][1]);
        if(to!=son[k]) g[k][1]+=f[to][0],g[k][0]+=max(f[to][0],f[to][1]);
    }
}
inline void Change(int u,int v){
    Matrix ch;ch.Clear();
    ch.a[1][0]+=-a[u]+v;a[u]=v;
    while(u){
        Matrix last=st.Ask(1,1,n,id[top[u]],id[en[u]])*ze;
        rep(i,0,1)rep(j,0,1)if(ch.a[i][j])st.Change(1,1,n,id[u],i,j,ch.a[i][j]);
        Matrix now=st.Ask(1,1,n,id[top[u]],id[en[u]])*ze;
        u=fa[top[u]];ch.Clear();
        ch.a[0][0]=-max(last.a[0][0],last.a[1][0])+max(now.a[0][0],now.a[1][0]);
        ch.a[0][1]=ch.a[0][0];
        ch.a[1][0]=-last.a[0][0]+now.a[0][0];
    }
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);read(m);rep(i,1,n) read(a[i]);
    rep(i,1,n-1){
        int u,v;read(u);read(v);e[u].pb(v);e[v].pb(u);
    }
    ze.Clear();ze.n=2;ze.m=1;
    dfs(1,0);Dfs(1,1);DFs(1);st.Build(1,1,n);
    rep(i,1,m){
        int u,v;read(u);read(v);
        Change(u,v);
        Matrix now=st.Ask(1,1,n,id[1],id[en[1]])*ze;
        printf("%d\n",max(now.a[0][0],now.a[1][0]));
    }
    return 0;
}

/*
+ 忘记 Build,Build 函数写错
+ 矩阵乘法写错
+ += 写成 =
+ 单位矩阵初始化错误
+ 1 写成 i 
+ 忘记 a[u]=v
*/

标签:typedef,int,rep,long,动态,DP,define
From: https://www.cnblogs.com/HeNuclearReactor/p/17552185.html

相关文章

  • luogu4_dp
    背包问题、线性动归、多维动归、技巧与记忆化《背包问题九讲》背包九讲01\完全\多重\混合01(每个物品仅1个总容量V不用装满)fori=1..n forj=V..v[i] ans[j]=max(ans[j],ans[j-v[i]]+w[i]);完全(商品可多次取)fori=1..n forj=v[i]..V//区别在此 ans[......
  • 【DP】DMOPC '21 Contest 8 P5 - Tree Building
    ProblemLink给定\(n,m\)和一个长为\(m\)的代价序列,对于一棵\(n\)个节点,每个节点度数不超过\(m\)的树,定义它的代价为\(\sum\limits_{i=1}^na_{deg_i}\)。求代价最小的树的代价。\(n\le10^9,m\le3000,1\lea_i\le10^9\)。首先一眼变成选出\(n\)个\(a\)的和为......
  • 【学习笔记】插头 DP
    插头DP,是一类解决网格图上连通性问题的状压DP。相关概念轮廓线:已经决策的方格和未决策方格之间的分界线。插头:用来描述连通性,一个方格与其某一方向的相邻方格连通,则称这个方格有某个方向的插头。容易发现在轮廓线上,每个时刻都是有\(n\)个上插头与\(1\)个左插头。如图,红......
  • 45. 动态规划
    一、什么是动态规划  动态规划(DynamicPorogramming)是算法的核心是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。动态规划与分治算法类似,不同的是,适用于动态规划求解的问题,经分解得到子问题往往不是互相独立的,即下一个子阶段的求解是建立在上一个子阶段的基础......
  • 2023-07-13 【动态规划】爬楼梯
    题目链接:爬楼梯详细:假设你正在爬楼梯。需要n阶你才能到达楼顶。每次你可以爬1或2个台阶。你有多少种不同的方法可以爬到楼顶呢?示例1:输入:n=2输出:2解释:有两种方法可以爬到楼顶。1阶+1阶2阶示例2:输入:n=3输出:3解释:有三种方法可以爬到楼顶。1阶......
  • 动态规划DP入门笔记
    动态规划以斐波那契数列为例:\(f_i\)状态\(f_i=f_{i-1}+f_{i-2}\)转移方程\(f_0=0\),\(f_1=1\)初始化dp的实现方法一般有三种,其中的两种(最重要的)如下#include<bits/stdc++.h>usingnamespacestd;intf[200010];signedmain(){ intn; scanf("%d",&n);......
  • C#动态编译计算
    示例代码:usingMicrosoft.CSharp;usingSystem;usingSystem.CodeDom.Compiler;usingSystem.Reflection;namespaceConsoleApp6{internalclassProgram{privatestaticvoidMain(string[]args){Expressione=newExpress......
  • C语言动态分配内存的函数
    今天在学习中碰见了动态分配内存有关的函数:mallocrealloccallocfree。以下是详细的记录"动态内存":在程序运行期间,动态分配内存空间,一般是在"堆,heap"空间上分配。malloc:memoryallocate内存分配realloc:repeatallocate再分配——重新分配:一次内存分配完成之后,后面用......
  • MyBatis动态表名和字段,减轻很大工作
    在动态sql解析过程,#{}与${}有本质差别1.#{}是基于JDBC的preparedStaement,${}是基于JDBC的Statement2.#{}表示的是预编译的参数,就是替代在SQL语句中的占位符‘?’,并会将参数作为字符串处理;如果要动态传入表名或者字段名,不能使用#{}3.#{}是使用预编译传参,可以预防SQL......
  • 高性能网络SIG月度动态:virtio-net 支持动态中断调节,SMC v2 协议增加新扩展
    高性能网络SIG(SpecialInterestGroup):在云计算时代,软硬件高速发展,云原生、微服务等新的应用形态兴起,让更多的数据在进程之间流动,而网络则成为了这些数据流的载体,在整个云时代扮演者前所未有的重要角色。在这个万物互联的时代,云上的网络通信效率对各种服务至关重要,高性能网......