首页 > 其他分享 >DDP学习笔记

DDP学习笔记

时间:2023-07-11 21:23:52浏览次数:41  
标签:return mat int DDP 矩阵 笔记 学习 改变 转移

概念

DDP,可以理解为转移会发生改变的动态规划。

当然这个改变是题目中给的,包括系数,转移位置的改变。显然暴力枚举这些改变是不现实的,我们要把改变体现到其他地方。

最经典的,体现到矩阵上。

我们把转移写成矩阵,那么改变转移就是改变转移矩阵。

具体的改变会落实到具体的题目上。

广义矩阵乘法

因为转移的多样性,矩阵乘法不一定需要用一般乘法的乘完相加。在满足结合律的情况下,可以是乘完取 \(\min\),加完取 \(\max\) 等。

如 CF750E,要删除最少,转移中需要取 \(\min\),所以写成矩阵时,重载乘法就用到了加完取 \(\min\),同时因为其有结合律,其仍旧可以像一般矩阵乘法进行上树等操作。

线段树维护

矩阵满足结合律,可以用线段树维护。

面对每一位转移不同的题目或者只需统计区间答案的题目时,使用线段树维护区间转移矩阵的积是很必要的。

主要是代码实现的难度。

struct mat
{
	int mat[6][6];
}a,c;
mat operator *(mat a,mat b)
{
    mat c;
    memset(c.mat,63,sizeof(c.mat));
    for(int k=0;k<5;k++)
    {
        for(int i=0;i<5;i++)
        {
            for(int z=0;z<5;z++)
            {
                c.mat[i][z]=min(c.mat[i][z],a.mat[i][k]+b.mat[k][z]);
            }
        }
    }
    return c;
}
mat mul(mat a,mat b)
{
    mat c;
    memset(c.mat,63,sizeof(c.mat));
    for(int k=0;k<5;k++)
    {
        for(int i=0;i<1;i++)
        {
            for(int z=0;z<5;z++)
            {
                c.mat[i][z]=min(c.mat[i][z],a.mat[i][k]+b.mat[k][z]);
            }
        }
    }
    return c;
}
int n,m,q,rt,w[200001];
mat sum[800001],inn;
void add(int o,int l,int r,int x,mat y)
{
    if(l==r)
    {
        sum[o]=y;
        return;
    }
    int mid=r+l>>1;
    if(x<=mid) add((o<<1),l,mid,x,y);
    else add((o<<1)+1,mid+1,r,x,y);
    sum[o]=sum[(o<<1)]*sum[(o<<1)+1];
}
mat get(int o,int l,int r,int x,int y)
{
    if(x<=l&&y>=r) return sum[o];
    int mid=l+r>>1;
    if(mid>=y)
    {
    	return get(o<<1,l,mid,x,y);
	}
	if(x>mid)
	{
		return get((o<<1)+1,mid+1,r,x,y);
	}
    return get(o<<1,l,mid,x,y)*get((o<<1)+1,mid+1,r,x,y);	
}

解决树上DDP问题

使用树链剖分把树断为链,重链内是序列问题可以自己解决。而重链之间的转移成为难点。

我们称一个重链顶与他的父亲组成一个卡口。改变一个点的值后,所有他到父亲的卡口值会改变。体现轻重链,我们设 \(g_u\) 为只与 \(u\) 亲儿子有关的转移,\(f_{uw}\) 为 \(u\) 的重儿子的 \(DP\) 值,我们必须把 \(f_u\) 转移写成只与 \(g_u\) 和 \(f_{uw}\) 有关的式子。

为什么呢?

保证时间复杂度,因为每个重链内是序列问题,它是不用改变的,而到了卡口,\(g\) 值会变。若和其他 \(f\) 有关,那么改变一个点的值将导致他到根的所有 \(f\) 值改变,因为他们的转移都依赖于此。

模板题

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
struct mat
{
	int mat[2][2];
}gg[100001];
mat operator *(mat a,mat b)
{
    mat c;
    for(int i=0;i<2;i++)
    {
    	for(int z=0;z<2;z++)
    	{
    		c.mat[i][z]=-100000000;
		}
	}
    for(int k=0;k<2;k++)
    {
        for(int i=0;i<2;i++)
        {
            for(int z=0;z<2;z++)
            {
                c.mat[i][z]=max(c.mat[i][z],a.mat[i][k]+b.mat[k][z]);
            }
        }
    }
    return c;
}
mat mul(mat a,mat b)
{
    mat c;
    for(int i=0;i<2;i++)
    {
    	for(int z=0;z<2;z++)
    	{
    		c.mat[i][z]=-100000000;
		}
	}
    for(int k=0;k<2;k++)
    {
        for(int i=0;i<1;i++)
        {
            for(int z=0;z<2;z++)
            {
                c.mat[i][z]=max(c.mat[i][z],a.mat[i][k]+b.mat[k][z]);
            }
        }
    }
    return c;
}
int n,m,q,rt,w[200001];
mat sum[800001];
int fat[100001],siz[100001],dep[100001],hson[100001],top[100001],cnt,dfn[100001],dis[100001],f[100001][2],downd[100001];
vector<int> g[1000001];
void add(int o,int l,int r,int x,mat y)
{
    if(l==r)
    {
        sum[o]=y;
        return;
    }
    int mid=r+l>>1;
    if(x<=mid) add((o<<1),l,mid,x,y);
    else add((o<<1)+1,mid+1,r,x,y);
    sum[o]=sum[(o<<1)]*sum[(o<<1)+1];
}
mat get(int o,int l,int r,int x,int y)
{
    if(x<=l&&y>=r) return sum[o];
    int mid=l+r>>1;
    if(mid>=y)
    {
    	return get(o<<1,l,mid,x,y);
	}
	if(x>mid)
	{
		return get((o<<1)+1,mid+1,r,x,y);
	}
    return get(o<<1,l,mid,x,y)*get((o<<1)+1,mid+1,r,x,y);	
}
void getdfsh(int u,int fa)
{
    fat[u]=fa;
    dep[u]=dep[fa]+1;
    int lll=0;
    f[u][1]=w[u];
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==fa) continue;
        getdfsh(v,u);
        if(siz[v]>lll)
        {
            hson[u]=v;
            lll=siz[v];
        }
        siz[u]+=siz[v];
	    f[u][1]+=f[v][0];
	    f[u][0]+=max(f[v][0],f[v][1]);
    }
    siz[u]++;
}
void gettd(int u,int fa)
{
	gg[u].mat[1][0]=w[u];
	gg[u].mat[1][1]=-100000000;
    dfn[u]=++cnt;
    dis[u]=cnt;
    if(hson[fat[u]]==u)
    {
        top[u]=top[fa];
        downd[top[u]]=dfn[u];
    }
    else
    {
        top[u]=u;
        downd[top[u]]=dfn[u];
    }
    if(hson[u]!=0) gettd(hson[u],u);
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==fa||v==hson[u]) continue;
        gettd(v,u);
	    gg[u].mat[0][0]+=max(f[v][0],f[v][1]);
	    gg[u].mat[1][0]+=f[v][0];
    }
  	gg[u].mat[0][1]=gg[u].mat[0][0];
}
void getdis(int u, int fa) {
    for(int i=0;i<g[u].size();i++)
	{
        int v=g[u][i];
        if (v==fa) continue;
        getdis(v,u);
        dis[u]=max(dis[u],dis[v]);
    }
}
void update(int x,int val)
{
 	gg[x].mat[1][0]+=val-w[x];
	w[x]=val;
 	while(x)
	{
   	 	mat las=get(1,1,n,dfn[top[x]],downd[top[x]]);
   	 	add(1,1,n,dfn[x],gg[x]);
   	 	mat now=get(1,1,n,dfn[top[x]],downd[top[x]]);
   	 	x=fat[top[x]];
   	 	gg[x].mat[0][0]+=max(now.mat[0][0],now.mat[1][0])-max(las.mat[0][0],las.mat[1][0]);
   	 	gg[x].mat[0][1]=gg[x].mat[0][0];
   	 	gg[x].mat[1][0]+=now.mat[0][0]-las.mat[0][0];
	}
}
signed main()
{
	scanf("%d",&n);
	scanf("%d",&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&w[i]);
    }
    for(int i=1,u,v;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    getdfsh(1,0);
    gettd(1,0);
    getdis(1,0);
    for(int i=1;i<=n;i++)
    {
    	add(1,1,n,dfn[i],gg[i]);
	}
  	for(int i=1;i<=m;i++)
	{
  	  	int x,val;
  	 	scanf("%d%d",&x,&val);
  		update(x,val);
  	 	mat ans=get(1,1,n,1,downd[1]);
  	 	printf("%d\n",max(ans.mat[0][0],ans.mat[1][0]));
	}
}

标签:return,mat,int,DDP,矩阵,笔记,学习,改变,转移
From: https://www.cnblogs.com/lizhous/p/17545951.html

相关文章

  • Markdown练习笔记
    一级标题二级标题三级标题四级标题五级标题六级标题斜体粗体粗斜体换行引用嵌套cker-博客园(cnblogs.com)https://www.cnblogs.com/ckeri/无序列表有序列表删除下划线code#include<bits/stdc++.h>usingnamespacestd;intmain(){cout<......
  • ASP.NET CORE 框架揭秘读书笔记系列——命令行程序的创建(一)
    一、dotnet--info查看本机开发环境dotnet--info 会显示本机安装的SDK版本、运行时环境、运行时版本二、利用命令行创建.NET项目我们不仅可以利用脚手架模版创建各种类型的应用项目,还可以为项目添加各种组件和配置。换句话说IDE能完成的各项工作全部都可以通过脚手架命令行......
  • [学习笔记] 割点 & 割边 & 双连通分量
    一、定义在无向连通图\(G=(V,E)\)中,若存在一个点\(u(u\inV)\)使得删掉点\(u\)及其相连的边,会使原图不连通,就称\(u\)是原图的一个割点(cutvertex);若存在一条边\((u,v)((u,v)\inE)\)满足删掉\((u,v)\)后会使原图不连通,就称\((u,v)\)是原图的一座桥(或......
  • CSAPP DataLab学习笔记
    1.bitXor/**bitXor-x^yusingonly~and&*Example:bitXor(4,5)=1*Legalops:~&*Maxops:14*Rating:1*/intbitXor(intx,inty){return2;}思路将异或的真值表写出来,再用&|~表示,最后化简代码intbitXor(intx,inty)......
  • es笔记四之中文分词插件安装与使用
    本文首发于公众号:Hunter后端原文链接:es笔记四之中文分词插件安装与使用前面我们介绍的操作及演示都是基于英语单词的分词,但我们大部分使用的肯定都是中文,所以如果需要使用分词的操作肯定也是需要使用中分分词。这里我们介绍一下如何安装中文分词插件。在介绍安装之前,我们可......
  • 我们与高效工作流的距离:使用AI阅读工具ChatDOC+笔记软件Obsidian Slide,直接从 PDF 文
    我们与高效工作流的距离在当今信息化的时代,为了实现高效工作和学习,如何实现快速地输入和输出成为每个人的必修课题。然而,对于输入而言,每一天大量的信息,往往会使我们陷入信息过载和知识爆炸的困境,难以高效处理。与此同时,输出方面的问题也同样令人头痛。对于多数人而言,PPT是主流......
  • 【做题笔记】线性dp——线段树优化
    线段树优化是用来对于\(DP\)数组区间赋值的。主要是区间取最值来优化线性dp真没什么可写的了挂两个题目:P4644[USACO05DEC]CleaningShiftsSP1545[USACO04DEC]DividingthePathGUSACO的小清新线段树优化dp好题......
  • 我们与高效工作流的距离:使用AI阅读工具ChatDOC+笔记软件Obsidian Slide,直接从 PDF 文
    我们与高效工作流的距离在当今信息化的时代,为了实现高效工作和学习,如何实现快速地输入和输出成为每个人的必修课题。然而,对于输入而言,每一天大量的信息,往往会使我们陷入信息过载和知识爆炸的困境,难以高效处理。与此同时,输出方面的问题也同样令人头痛。对于多数人而言,PPT是主流的输......
  • 学习第一天
    day01什么是计算机Computer:全称电子计算机,俗称电脑。能够按照程序红色运行,自动、高速处理海量数据的现代化智能电子设备。由硬件和软件组成。常见的形式有台式计算机、笔记本计算机、大型计算机等。广泛应用在:科学计算、数据处理、自动控制、计算机辅助设计,人工智能,网络等......
  • Docker学习路线2:底层技术
    了解驱动Docker的核心技术将让您更深入地了解Docker的工作原理,并有助于您更有效地使用该平台。Linux容器(LXC)Linux容器(LXC)是Docker的基础。LXC是一种轻量级的虚拟化解决方案,允许多个隔离的Linux系统在单个主机上运行,无需全功能的虚拟化。LXC有效地以安全和优化的方式隔离应用程......