bzoj 4712 洪水——动态DP

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4712

因为作为动态DP练习而找到,所以就用动态DP做了,也没管那种二分的方法。

感觉理解似乎加深了。

果然初始权值也都是非负的。

所以 dp[cr] 表示当前子树与自己的叶子都断开了的最小代价,则 dp[cr]=min{ sigma dp[v] , w[cr] }(v是cr的直接孩子)。

但这样的话,修改的时候需要把自己到根的路径都走一遍。不过查询是O(1)的,所以考虑分配一下。

走到根的过程如果是 log 的话就好了。那么不是倍增就是树剖。

考虑用树剖,s[cr] 表示 sigma dp[v] ( v是cr的轻儿子)。这样修改的话只要每次遇到别的重链就改一下它的 s 就行了。

考虑查询,可以从矩阵的角度看:

  状态矩阵是2行1列,放 dp[cr] 和 0 ;转移矩阵是2行2列,[0][0]=s[cr],[0][1]=w[cr],[1][0]=0,[1][1]=0。转移的时候是[ i ][ j ]=min( [ i ][ j ] , [ i ][ k ]+[ k ][ j ] )。

于是树剖的线段树维护的就是转移矩阵的乘积,查询一个点到其所在重链底端的一段乘积即可。原本要乘一个状态,但那个是 [0][0]=0,[0][1]=0,所以把2行2列的 [0][0] 和 [0][1] 取个min作为dp[ ]。

然后就能以很慢的速度A了。

或者像这个人这样,好像能快个2504ms。https://www.cnblogs.com/GXZlegend/p/8710445.html

自己生硬地弄2×2矩阵果然不够好吗……这也启示我们,只要是线段树能维护的东西就行,不一定非是矩阵。关键是把轻儿子的信息带在身上,现求重儿子的信息。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define ls Ls[cr]
#define rs Rs[cr]
using namespace std;
const int N=2e5+5,INF=1e9+5;
int n,m,hd[N],xnt,to[N<<1],nxt[N<<1],w[N];
int tot,dfn[N],rnk[N],top[N],son[N],siz[N],fa[N],bj[N],Ls[N<<1],Rs[N<<1];
ll dp[N];
ll Mn(ll a,ll b){return a<b?a:b;}
struct Matrix{
  ll a[2][2];
  Matrix(){a[0][0]=a[0][1]=a[1][0]=a[1][1]=INF;}
  Matrix operator+ (const Matrix &b)const
  {
    Matrix c;
    for(int i=0;i<=1;i++)
      for(int k=0;k<=1;k++)
    for(int j=0;j<=1;j++)
      c.a[i][j]=Mn(c.a[i][j],a[i][k]+b.a[k][j]);
    return c;
  }
}g[N],t[N<<1];
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>‘9‘||ch<‘0‘){if(ch==‘-‘)fx=0;ch=getchar();}
  while(ch>=‘0‘&&ch<=‘9‘) ret=(ret<<3)+(ret<<1)+ch-‘0‘,ch=getchar();
  return fx?ret:-ret;
}
void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;}
void dfs(int cr)
{
  siz[cr]=1;
  for(int i=hd[cr],v;i;i=nxt[i])
    if((v=to[i])!=fa[cr])
      {
    fa[v]=cr; dfs(v); siz[cr]+=siz[v];
    siz[v]>siz[son[cr]]?son[cr]=v:0;
      }
}
void dfsx(int cr)
{
  dfn[cr]=++tot; rnk[tot]=cr;
  dp[cr]=w[cr]; ll tmp=0;
  if(son[cr])top[son[cr]]=top[cr],dfsx(son[cr]);
  g[cr].a[0][0]=INF; g[cr].a[0][1]=w[cr];
  g[cr].a[1][0]=g[cr].a[1][1]=0;
  if(!son[cr]){bj[top[cr]]=dfn[cr];return;}

  for(int i=hd[cr],v;i;i=nxt[i])
    if((v=to[i])!=fa[cr]&&v!=son[cr])
      {
    top[v]=v;dfsx(v);
    tmp+=dp[v];
      }
  g[cr].a[0][0]=tmp; dp[cr]=Mn(w[cr],tmp+dp[son[cr]]);
}
void build(int l,int r,int cr)
{
  if(l==r){t[cr]=g[rnk[l]];return;}
  int mid=l+r>>1;
  ls=++tot; build(l,mid,ls);
  rs=++tot; build(mid+1,r,rs);
  t[cr]=t[ls]+t[rs];
}
void updt(int l,int r,int cr,int p)
{
  if(l==r){t[cr]=g[rnk[l]];return;}
  int mid=l+r>>1;
  if(p<=mid)updt(l,mid,ls,p);
  else updt(mid+1,r,rs,p);
  t[cr]=t[ls]+t[rs];
}
Matrix query(int l,int r,int cr,int L,int R)
{
  if(l>=L&&r<=R)return t[cr];
  int mid=l+r>>1;
  if(R<=mid)return query(l,mid,ls,L,R);
  if(mid<L)return query(mid+1,r,rs,L,R);
  return query(l,mid,ls,L,R)+query(mid+1,r,rs,L,R);
}
Matrix calc(int cr){ return query(1,n,1,dfn[cr],bj[cr]);}
void cz(int x,int y)
{
  g[x].a[0][1]+=y;
  Matrix k1=calc(top[x]); updt(1,n,1,dfn[x]); Matrix k2=calc(top[x]);
  while(fa[top[x]])
    {
      g[fa[top[x]]].a[0][0]+=Mn(k2.a[0][0],k2.a[0][1])-Mn(k1.a[0][0],k1.a[0][1]);
      x=fa[top[x]];
      k1=calc(top[x]); updt(1,n,1,dfn[x]); k2=calc(top[x]);
    }
}
int main()
{
  n=rdn();for(int i=1;i<=n;i++)w[i]=rdn();
  for(int i=1,u,v;i<n;i++)
    {
      u=rdn(); v=rdn(); add(u,v); add(v,u);
    }
  dfs(1); top[1]=1; dfsx(1); tot=1; build(1,n,1);
  m=rdn();  char ch[5];
  for(int i=1,x,y;i<=m;i++)
    {
      scanf("%s",ch);
      if(ch[0]==‘C‘)
    {
      x=rdn(); y=rdn(); cz(x,y);
    }
      else
    {
      x=rdn(); Matrix d=query(1,n,1,dfn[x],bj[top[x]]);
      printf("%lld\n",Mn(d.a[0][0],d.a[0][1]));
    }
    }
  return 0;
}

原文地址:https://www.cnblogs.com/Narh/p/10004192.html

时间: 2024-07-30 07:38:10

bzoj 4712 洪水——动态DP的相关文章

4712: 洪水 基于链分治的动态DP

国际惯例的题面:看起来很神的样子......如果我说这是动态DP的板子题你敢信?基于链分治的动态DP?说人话,就是树链剖分线段树维护DP.既然是DP,那就先得有转移方程.我们令f[i]表示让i子树中的叶子节点全部与根不联通,所需要的最小代价,v[i]为输入的点权.显然f[i]=min(v[i],sigma(f[soni])),边界条件是,如果i是叶子节点,则f[i]=v[i].我们需要用链分治去维护这个DP,所以要把DP拆成重链和轻链独立的形式.我们还是用f[i]表示让i子树中的叶子节点全部与根

BZOJ 1087状态压缩DP

状态压缩DP真心不会写,参考了别人的写法. 先预处理出合理状态, 我们用二进制表示可以放棋子的状态,DP[I][J][K]:表示现在处理到第I行,J:表示第I行的状态,K表示现在为止一共放的棋子数量. #include<stdio.h> #include<iostream> #define N 1111 using namespace std; typedef long long ll; int num,n,m; ll dp[11][1<<11][90]; int hh

主席树初探 &amp; bzoj 3295: [Cqoi2011] 动态逆序对 题解

[原题] 3295: [Cqoi2011]动态逆序对 Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 778  Solved: 263 [Submit][Status] Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. Input 输入第一行包含两个整数n和m,即初始元素的个数和删除的元

BZOJ 1237 配对(DP)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1237 题意:给出两个n元素的数列A和B.为A中的每个元素在B中为其找一个配对的元素(配对的元素不能相等,B中每个元素只能被使用一次),使得所有配对的两个数的差的绝对值之和最小? 思路:首先排序,若无配对元素不能相等这一 限制,则排序后一一匹配即可.有了这个限制,那么对于相等的元素就要在其前后进行调整,显然,与距离较远的调整不如与距离近的调整优.那么,需要在多大的 范围内调整才能保证最优

bzoj 3437 斜率优化DP

写题解之前首先要感谢妹子. 比较容易的斜率DP,设sum[i]=Σb[j],sum_[i]=Σb[j]*j,w[i]为第i个建立,前i个的代价. 那么就可以转移了. 备注:还是要感谢妹子. /************************************************************** Problem: 3437 User: BLADEVIL Language: C++ Result: Accepted Time:3404 ms Memory:39872 kb **

[动态dp]线段树维护转移矩阵

背景:czy上课讲了新知识,从未见到过,总结一下. 所谓动态dp,是在动态规划的基础上,需要维护一些修改操作的算法. 这类题目分为如下三个步骤:(都是对于常系数齐次递推问题) 1先不考虑修改,不考虑区间,直接列出整个区间的dp方程.这个是基础,动态dp无论如何还是dp(这一步是一般是重点) 2.列出转移矩阵.由于有很多修改操作,我们将数据集中在一起处理,还可以利用矩阵结合律,并且区间比较好提取,(找一段矩阵就好了),修改也方便. 3.线段树维护矩阵.对于修改,我们就是在矩阵上进行修改,对于不同的

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

题目分析: 不难发现可以用动态DP做. 题目相当于是要我求一条路径,所有与路径有交的链的代价加入进去,要求代价最大. 我们把链的代价分成两个部分:一部分将代价加入$LCA$之中,用$g$数组保存:另一部分将代价加在整条链上,用$d$数组保存. 这时候我们可以发现,一条从$u$到$v$的路径的代价相当于是$d[LCA(u,v)]+\sum_{x \in edge(u,v)}g[x]$. 如果是静态的,可以用树形DP解决. 看过<神奇的子图>的同学都知道,叶子结点是从它的儿子中取两个最大的出来,所

uoj#268. 【清华集训2016】数据交互(动态dp+堆)

传送门 动态dp我好像还真没咋做过--通过一个上午的努力光荣的获得了所有AC的人里面的倒数rk3 首先有一个我一点也不觉得显然的定理,如果两条路径相交,那么一定有一条路径的\(LCA\)在另一条路径上 于是我们可以对于每一个点记录两个值,一个\(a_i\)表示\(LCA\)在\(i\)点的所有路径的权值之和,一个是\(b_i\),表示经过点\(i\)且\(LCA\)不在点\(i\)的所有路径的权值之和 那么对于一条路径\((u,v)\),它的权值就是\(b_{LCA(u,v)}+\sum_{i\

回文串 --- 动态dp UVA 11584

题目链接: https://cn.vjudge.net/problem/34398/origin 本题的大意其实很简单,就是找回文串,大致的思路如下: 1. 确定一个回文串,这里用到了自定义的check函数原理如下: 传入le, ri两个值(定义从1开始), s+1 = aaadbccb. a a a d b c c b 1 2 3 4 5 6 7 8 比如,le = 5, ri = 8. 则s[5] == s[8]成立 le++ ri-- 再比较 s[6] == s[7]? 成立 le++,