DDP入门

DDP,即动态动态规划,可以用于解决一类带修改的DP问题。
我们从一个比较简单的东西入手,最大子段和。
带修改的最大子段和其实是常规问题了,经典的解决方法是用线段树维护从左,右开始的最大子段和和区间最大子段和,然后进行合并。
现在我们换一种方法来解决它。我们假设\(f[i]\)表示以i为结尾的最大子段和大小,\(g[i]\)表示[1,i]的最大子段和大小,显然有转移:\(f[i] = max(f[i-1]+a[i],a[i]),g[i] = max(g[i-1],f[i])\)

这个DP每次修改显然要\(O(n)\)
我们考虑到好多在DP的时候,我们都用矩阵来加速递推。
我们现在引入全新的思想,如何将它改写成矩阵呢?
其实矩阵乘法能够成立,依赖的是乘法对加法有分配律。之后我们发现,加法对取\(min/max\)的操作也是有分配律的。比如\(a+max(b,c) = max(a+b,a+c)\)
那么我们完全可以考虑重新定义矩阵乘法,使得其满足如下的运算:\(C[i][j] = max\{A[i][k]+B[k][j]\}\)

这样的话……刚才的转移方程,我们就可以改写成如下的形式了。
\[\begin{bmatrix} a_i & -\infty & a_i \\ a_i & 0 &a_i \\ -\infty & -\infty & 0\end{bmatrix}\times \begin{bmatrix} f_{i-1}\\ g_{i-1} \\ 0\end{bmatrix}\quad = \begin{bmatrix}f_i \\ g_i \\ 0\end{bmatrix}\]

那么我们就可以用线段树维护区间矩阵乘积来计算答案了。

#include<bits/stdc++.h>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')
#define fr friend inline
#define y1 poj
#define pr pair<int,int>
#define fi first
#define sc second
#define mp make_pair

using namespace std;
typedef long long ll;
const int M = 200005;
const int INF = 1e9+7;
const double eps = 1e-7;

int read()
{
   int ans = 0,op = 1;char ch = getchar();
   while(ch < '0' || ch > '9') {if(ch == '-') op = -1;ch = getchar();}
   while(ch >= '0' && ch <= '9') ans = ans * 10 + ch - '0',ch = getchar();
   return ans * op;
}

struct matrix
{
   int f[3][3];
   matrix(){memset(f,0,sizeof(f));}
   void change(int x)
   {
      f[0][0] = f[1][0] = f[0][2] = f[1][2] = x;
      f[0][1] = f[2][0] = f[2][1] = -INF;
   }
   friend matrix operator + (const matrix &a,const matrix &b)
   {
      matrix c;
      rep(i,0,2) rep(j,0,2) c.f[i][j] = -INF;
      rep(k,0,2)
      rep(i,0,2)
      rep(j,0,2)
      c.f[i][j] = max(c.f[i][j],a.f[i][k] + b.f[k][j]);
      return c;
   }
};

struct node
{
   matrix mat;
}t[M<<2];

int n,q,x,y,op;

void build(int p,int l,int r)
{
   if(l == r) {t[p].mat.change(read());return;}
   int mid = (l+r) >> 1;
   build(p<<1,l,mid),build(p<<1|1,mid+1,r);
   t[p].mat = t[p<<1].mat + t[p<<1|1].mat;
}

void modify(int p,int l,int r,int pos,int val)
{
   if(l == r) {t[p].mat.change(val);return;}
   int mid = (l+r) >> 1;
   if(pos <= mid) modify(p<<1,l,mid,pos,val);
   else modify(p<<1|1,mid+1,r,pos,val);
   t[p].mat = t[p<<1].mat + t[p<<1|1].mat;
}

matrix query(int p,int l,int r,int kl,int kr)
{
   if(l == kl && r == kr) return t[p].mat;
   int mid = (l+r) >> 1;
   if(kr <= mid) return query(p<<1,l,mid,kl,kr);
   else if(kl > mid) return query(p<<1|1,mid+1,r,kl,kr);
   else return query(p<<1,l,mid,kl,mid) + query(p<<1|1,mid+1,r,mid+1,kr);
}

int main()
{
   n = read(),build(1,1,n),q = read();
   while(q--)
   {
      op = read(),x = read(),y = read();
      if(op == 0) modify(1,1,n,x,y);
      else
      {
     matrix k = query(1,1,n,x,y);
     printf("%d\n",max(k.f[1][0],k.f[1][2]));
      }
   }
   return 0;
}

之后我们再来考虑下一个问题。树上最大独立集。
\(f[i][0]\)表示不选i,子树中最大独立集的大小,\(f[i][1]\)表示选i,子树中最大独立集的大小。
显然有\(f[i][0] = \sum max(f[v][0],f[v][1]),f[i][1] = \sum f[v][0] + a[i]\)
我们要把这玩意改写成矩阵的形式。但是我们首先要使用数据结构维护树,比如树剖。(LCT版的我还不会)
因为树剖可以把重链整成一段连续的区间,那么我们先把与重链无关的一些东西提取出来。这样,我们设\(g[i][0/1]\)表示不取/取i,i的非重儿子中最大独立集的大小
这样的话,dp的方程就变成了这样:\(f[i][0] =g[i][0] + max(f[son[i]][0],f[son[i]][1]),f[i][1] = g[i][1] + f[son[i]][0]\)
然后就可以开心的写成矩阵的形式:
\[\begin{bmatrix} g[i][0] & g[i][0] \\ g[i][1] & -\infty\end{bmatrix}\times \begin{bmatrix} f[son[i][0]]\\ f[son[i]][1] \end{bmatrix}= \begin{bmatrix}f[i][0] \\ f[i][1]\end{bmatrix}\]

那么现在我们就可以用树剖+矩阵去维护了。这个和普通的树剖有一些区别,就是我们需要先跑一次树DP来计算出来f,g数组,之后初始化矩阵,每次从修改点跳重链跳到根节点,注意每次跳重链的时候要取一段完整的重链,所以我们还需要额外记录链的底部在哪。
然后就不大难修改了。线段树和上面基本是一样的,树剖也比较简单,修改过程就是一个先减再加的过程。
看一下luogu的模板

#include<bits/stdc++.h>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')
#define pr pair<int,int>
#define mp make_pair
#define fi first
#define sc second
using namespace std;
typedef long long ll;
const int M = 200005;
const int N = 10000005;
const int INF = 1e9;

int read()
{
   int ans = 0,op = 1;char ch = getchar();
   while(ch < '0' || ch > '9') {if(ch == '-') op = -1;ch = getchar();}
   while(ch >='0' && ch <= '9') ans = ans * 10 + ch - '0',ch = getchar();
   return ans * op;
}

struct matrix
{
   int f[2][2];
   matrix(){memset(f,0,sizeof(f));}
   friend matrix operator + (const matrix &a,const matrix &b)
   {
      matrix c;
      rep(i,0,1)
      rep(j,0,1) c.f[i][j] = -INF;
      rep(k,0,1)
      rep(i,0,1)
      rep(j,0,1) c.f[i][j] = max(c.f[i][j],a.f[i][k] + b.f[k][j]);
      return c;
   }
}val[M];

struct node
{
   matrix mat;
}t[M<<1];

struct edge
{
   int next,to,from;
}e[M<<1];

int n,m,head[M],ecnt,v[M],top[M],fa[M],hson[M],size[M];
int ed[M],x,y,pos[M],dfn[M],idx,F[M][2];
void add(int x,int y) {e[++ecnt] = {head[x],y,x},head[x] = ecnt;}

void dfs1(int x,int f)
{
   size[x] = 1,fa[x] = f;
   for(int i = head[x];i;i = e[i].next)
   {
      if(e[i].to == f) continue;
      dfs1(e[i].to,x),size[x] += size[e[i].to];
      if(size[e[i].to] > size[hson[x]]) hson[x] = e[i].to;
   }
}

void dfs2(int x,int t)
{
   dfn[x] = ++idx,pos[idx] = x,top[x] = t,ed[t] = max(ed[t],idx);
   F[x][0] = 0,F[x][1] = v[x];
   val[x].f[0][0] = val[x].f[0][1] = 0,val[x].f[1][0] = v[x];
   if(hson[x])
   {
      int v = hson[x];
      dfs2(v,t),F[x][0] += max(F[v][0],F[v][1]),F[x][1] += F[v][0];
   }
   for(int i = head[x];i;i = e[i].next)
   {
      int v = e[i].to;
      if(v == fa[x] || v == hson[x]) continue;
      dfs2(v,v),F[x][0] += max(F[v][0],F[v][1]),F[x][1] += F[v][0];
      val[x].f[0][0] += max(F[v][0],F[v][1]);
      val[x].f[0][1] = val[x].f[0][0],val[x].f[1][0] += F[v][0];
   }
}

void build(int p,int l,int r)
{
   if(l == r) {t[p].mat = val[pos[l]];return;}
   int mid = (l+r) >> 1;
   build(p<<1,l,mid),build(p<<1|1,mid+1,r);
   t[p].mat = t[p<<1].mat + t[p<<1|1].mat;
}

void modify(int p,int l,int r,int x)
{
   if(l == r){t[p].mat = val[pos[x]];return;}
   int mid = (l+r) >> 1;
   if(x <= mid) modify(p<<1,l,mid,x);
   else modify(p<<1|1,mid+1,r,x);
   t[p].mat = t[p<<1].mat + t[p<<1|1].mat;
}

matrix query(int p,int l,int r,int kl,int kr)
{
   if(l == kl && r == kr) return t[p].mat;
   int mid = (l+r) >> 1;
   if(kr <= mid) return query(p<<1,l,mid,kl,kr);
   else if(kl > mid) return query(p<<1|1,mid+1,r,kl,kr);
   else return query(p<<1,l,mid,kl,mid) + query(p<<1|1,mid+1,r,mid+1,kr);
}

void uprange(int x,int y)
{
   val[x].f[1][0] += y - v[x],v[x] = y;
   matrix A,B;
   while(x)
   {
      B = query(1,1,n,dfn[top[x]],ed[top[x]]),modify(1,1,n,dfn[x]);
      A = query(1,1,n,dfn[top[x]],ed[top[x]]),x = fa[top[x]];
      val[x].f[0][0] += max(A.f[0][0],A.f[1][0]) - max(B.f[0][0],B.f[1][0]);
      val[x].f[0][1] = val[x].f[0][0];
      val[x].f[1][0] += (A.f[0][0] - B.f[0][0]);
   }
}

int main()
{
   n = read(),m = read();
   rep(i,1,n) v[i] = read();
   rep(i,1,n-1) x = read(),y = read(),add(x,y),add(y,x);
   dfs1(1,0),dfs2(1,1),build(1,1,n);
   while(m--)
   {
      x = read(),y = read(),uprange(x,y);
      matrix ans = query(1,1,n,dfn[1],ed[1]);
      printf("%d\n",max(ans.f[0][0],ans.f[1][0]));
   }
   return 0;
}

转载于:https://www.cnblogs.com/captain1/p/10459348.html

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PyTorch的DDP(Distributed Data Parallel)是一种多机多卡训练方法,它通过提高batch size来增加并行度,从而加快模型训练速度。DDP使用了一种称为Ring-Reduce的数据交换方法,这种方法提高了通信效率,并且通过启动多个进程的方式减轻了Python GIL(全局解释器锁)的限制。因此,DDP通常比DP(Data Parallel)更快,能够实现略低于使用的卡数的加速比(例如,在四卡下可能会加速3倍)。因此,DDP是目前最流行的多机多卡训练方法之一。 在使用DDP时,你只需要在代码中添加一行简单的语句即可使用。具体来说,你需要将你的模型包装在DDP函数中,并指定设备ID(device_ids)和输出设备(output_device)。这样就可以启用DDP,并在多机多卡环境中运行模型训练。 如果你需要了解更多关于PyTorch DDP的详细信息,可以参考一些相关的教程和示例代码,例如《PyTorch分布式训练简明教程》和《PyTorch多机多卡分布式训练》。这些资源可以帮助你更好地理解和使用PyTorch的DDP功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Pytorch中的DDP](https://blog.csdn.net/flyingluohaipeng/article/details/127900749)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值