树形DP&环形和后效性处理

树形dp


概念

依然是 O ( n ) O(n) O(n)类型的dp,通过dfs或bfs实现小状态向大状态的转移,本质是在树上更方便表示的线性动规

例题

P2014 [CTSC1997]选课

是做过的题,就不详细说了

树上的分组背包

POJ3585

从树上选取一个源点作为根,每条边都有一个容量,该树对应的叶子结点为汇点

问应当选取哪个点为源点,该树的流量最大

朴素算法:

枚举每个点作为源点,用树形dp求出最大流量

也就是从下往上取min

d[x]表示以 x x x为根的子树所得的最大流量
d [ x ] = ∑ y ∈ s o n ( x ) { m i n ( d [ y ] , c ( x , y ) ) ( y 的 度 数 > 1 ) c ( x , y ) ( y 的 度 数 为 1 ) d[x]=\sum_{y\in son(x)}\begin{cases} min(d[y],c(x,y))(y的度数>1)\\ c(x,y)(y的度数为1)\\ \end{cases} d[x]=yson(x){min(d[y],c(x,y))(y>1)c(x,y)(y1)

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+7;
vector<int> son[N];
int d[N],deg[N];
bool vis[N];
int n,T;
void dp(int x){
    d[x]=0,v[x]=1;
    for(int i=0;i<son[x].size();i++){
        int y=son[x][i];
        if(vis[y]) continue;
        dp(y);
        if(deg[y]==1) d[x]+=val[x][i];
        else d[x]+=min(d[x],d[y]+val[x][i]);
    }
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        memset(d,0,sizeof(d));
        memset(vis,0,sizeof(vis));
        memset(deg,0,sizeof(deg));
        //...
    }
    return 0;
}

是否存在冗余呢?

答案是肯定的,因为每条边只有正向反向两种情况

本质只有 2 n 2n 2n种情况

二次扫描&换根法

我们首先以 x x x为根得到d[i],求以y为根的结果f[i]

我们会得到:
f [ y ] = d [ y ] + { m i n ( f [ x ] − m i n ( d [ y ] , c ( x , y ) ) , c ( x , y ) )          ( d e g [ x ] > 1 , d e g [ y ] > 1 ) m i n ( f [ x ] − c ( x , y ) , c ( x , y ) )                            ( d e g [ x ] > 1 , d e g [ y ] = 1 ) c ( x , y )                                                               ( d e g [ x ] = 1 ) f[y]=d[y]+\begin{cases} min(f[x]-min(d[y],c(x,y)),c(x,y))\ \ \ \ \ \ \ \ (deg[x]>1,deg[y]>1)\\ min(f[x]-c(x,y),c(x,y))\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (deg[x]>1,deg[y]=1)\\ c(x,y)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (deg[x]=1)\\ \end{cases} f[y]=d[y]+min(f[x]min(d[y],c(x,y)),c(x,y))        (deg[x]>1,deg[y]>1)min(f[x]c(x,y),c(x,y))                          (deg[x]>1,deg[y]=1)c(x,y)                                                             (deg[x]=1)
复杂度 O ( n ) O(n) O(n)

环形dp


概念

对于可拆解的环,我们有 O ( n ) O(n) O(n)做法枚举断点

我们的目的是更加高效的处理这一类问题

例题

Acwing288. 休息时间

我们一天有 n n n个小时,可以选择 b b b个小时休息,其中每段休息的第一个小时为入睡时间,每个睡着的小时可以活的u[i]点体力

朴素算法:

f[i][j][0/1]表示前 i i i小时选 j j j小时休息,其中第 i i i小时是否休息所获得的最大体力

f [ i ] [ j ] [ 0 ] = m a x ( f [ i − 1 ] [ j ] [ 0 ] , f [ i − 1 ] [ j ] [ 1 ] ) f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]) f[i][j][0]=max(f[i1][j][0],f[i1][j][1])

f [ i ] [ j ] [ 1 ] = m a x ( f [ i − 1 ] [ j − 1 ] [ 0 ] , f [ i − 1 ] [ j − 1 ] [ 1 ] + u [ i ] ) f[i][j][1]=max(f[i-1][j-1][0],f[i-1][j-1][1]+u[i]) f[i][j][1]=max(f[i1][j1][0],f[i1][j1][1]+u[i])

初值: f [ 1 , 0 , 0 ] = f [ 0 , 0 , 0 ] = 0 , f [ 1 ] [ 1 ] [ 1 ] = f [ 0 ] [ 0 ] [ 0 ] = 0 f[1,0,0]=f[0,0,0]=0,f[1][1][1]=f[0][0][0]=0 f[1,0,0]=f[0,0,0]=0,f[1][1][1]=f[0][0][0]=0

其中 m a x ( f [ n ] [ b ] [ 0 ] , f [ n ] [ b ] [ 1 ] ) max(f[n][b][0],f[n][b][1]) max(f[n][b][0],f[n][b][1])即为所求

这样我们便处理了一个序列上的问题

不难发现我们漏掉了u[1]的收益
总 状 态 = { 第 1 小 时 没 睡 着 第 1 小 时 睡 着 总状态=\begin{cases} 第1小时没睡着\\ 第1小时睡着\\ \end{cases} ={11
对于没考虑到的情况,我们令 f [ 1 ] [ 1 ] [ 1 ] = u [ 1 ] f[1][1][1]=u[1] f[1][1][1]=u[1]

进行dp,最后目标为 f [ n ] [ b ] [ 1 ] f[n][b][1] f[n][b][1]

两种情况互补,我们取max即可

复杂度 O ( n 2 ) O(n^2) O(n2)

本题中 1 1 1 n n n之间存在限制条件

常用的方法为分类讨论,将该限制条件拆分为满足和不满足,求两次dp

Acwing289. 环路运输

环形公路上有 n n n个仓库,第 i i i个存有 a i a_i ai个货物

我们定义 d i s t ( i , j ) = m i n ( ∣ i − j ∣ , N − ∣ i − j ∣ ) dist(i,j)=min(|i-j|,N-|i-j|) dist(i,j)=min(ij,Nij)

求那两座城市运输货物的代价最大

我们把 1 1 1~ n n n的序列复制一倍,其中长度为 n n n的一段即为一种情况

a n s = a [ i ] + i + m a x i − n / 2 ≤ j < i { a [ j ] − j } ans=a[i]+i+max_{i-n/2\leq j<i} \{a[j]-j \} ans=a[i]+i+maxin/2j<i{a[j]j}

max内的部分用单调队列维护

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+7;
int a[N],q[N];
int n,ans;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int l=1,r=1;
    q[1]=0;
    for(int i=1;i<=2*n;i++){
        while(l<=r&&q[l]<i-n/2) l++;
        ans=max(ans,a[i]+i+a[q[l]]-q[l]);
        while(l<=r&&a[i]-i<=a[q[r]]-q[r]) r--;
        q[++r]=i;
    }
    printf("%d\n",ans);
}

后效性处理


处理方法

高斯消元法: 我们把每个状态看做未知数,每个转移看做方程,运用高斯消元求解

SPFA: dp后效性的本质在于图上有环,SPFA可以多次遍历某个点

例题

Acwing290. 坏掉的机器人

给定一张 N × M N×M N×M 的棋盘,有一个机器人处于 ( x , y ) (x,y) (x,y) 位置

这个机器人可以进行很多轮行动,每次等概率地随机选择停在原地,向左移动一格,向右移动一格或向下移动一格

当然机器人不能移出棋盘

求机器人从起点走到最后一行的任意一个位置上,所需行动次数的数学期望值

f [ i ] [ j ] = 1 4 ( f [ i ] [ j ] + f [ i ] [ j − 1 ] + f [ i ] [ j + 1 ] + f [ i + 1 ] [ j ] ) + 1 f[i][j]=\frac{1}{4}(f[i][j]+f[i][j-1]+f[i][j+1]+f[i+1][j])+1 f[i][j]=41(f[i][j]+f[i][j1]+f[i][j+1]+f[i+1][j])+1

我们得到一个 m × m m\times m m×m的矩阵进行高斯消元

复杂度 O ( n × m 3 ) O(n\times m^3) O(n×m3)

posted @ 2021-05-31 21:49  actypedef  阅读(164)  评论(0编辑  收藏  举报