区间dp与树形dp专题学习心得

区间dp:无非是枚举长度,然后处理合并信息;

树形dp:处理出子树信息,用子树信息维护当前节点信息

区间dp的难点在于处理合并信息;O(n^{3})

树形dp难点在于 子树信息到当前节点信息的状态转移;O(n^{2})

区间dp题目清单:

石子合并 - 洛谷 P1880 - Virtual Judge

石子合并是最简单的区间dp,合并信息的处理简单:为选取某点,作为区间分割点,维护两边信息

从这一点出发,我们可以迁移到树上,对于dfs序的中序遍历来说,枚举顺序为左子树,节点,右子树,那么区间合并也可以作用到上面,来维护合并信息,题目见找不到页面 - AcWing加分二叉树

下面看一些合并信息复杂一些的题目:

Rectangle Painting 1 - CodeForces 1198D - Virtual Judge

这道题是维护矩阵信息,我们枚举分割线,将左右分割块的信息相加即可得到区间块信息,这里记忆化搜索加速

#include <bits/stdc++.h>

using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;
const int N=51,mod=1e9+7;
int n;
char g[N][N];
int f[N][N][N][N];

int dfs(int x1, int y1, int x2, int y2){
    if(f[x1][y1][x2][y2]!=-1){
        return f[x1][y1][x2][y2];
    }
    int v=max(y2-y1+1,x2-x1+1);
    //立着
    for(int i=x1; i<x2; i++){
        v=min(v,dfs(x1,y1,i,y2)+dfs(i+1,y1,x2,y2));
    }
    
    //横着
    for(int j=y1; j<y2; j++){
        v=min(v,dfs(x1,y1,x2,j)+dfs(x1,j+1,x2,y2));
    }
    f[x1][y1][x2][y2]=v;
    return v;
    
}


void solve() {

    cin>>n;
    memset(f,-1,sizeof f);
    for(int i=1; i<=n; i++){
        for(int j=1; j<=n; j++){
            cin>>g[i][j];
            f[i][j][i][j]=(g[i][j]=='#');
        }
    }
    cout<<dfs(1,1,n,n)<<endl;
    
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);


 

  
        solve();
    

    return 0;
}

https://vjudge.net/problem/CodeForces-607B/origin

信息为:最小删除次数,我们维护[l,r]的最小次数,枚举分割点l<=i<r,但是区间[l,r]的最优解不一定为[l,i]+[i+1,r],因为可能l和r的字符一致,导致最优解可能为[l+1,r-1](这个区间枚举长度时已经处理过),但是枚举i时没有处理这个信息,于是我们需要特判一下,维护信息。

#include <bits/stdc++.h>

using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;

void solve() {
    int n;
    cin>>n;
    vector<int>a(n+1);
    for(int i=1; i<=n; i++)cin>>a[i];
    vector<vector<int>>f(n+1,vector<int>(n+1,(int)1e9+10));
    for(int len=1; len<=n; len++){
        for(int i=1; i+len-1<=n; i++){
            int j=i+len-1;
            if(i==j){
                f[i][j]=1;
            }else{
                if(a[i]==a[j]){
                    if(i+1==j){
                        f[i][j]=1;
                    }
                    else f[i][j]=min(f[i][j],f[i+1][j-1]);
                }
                for(int k=i; k<j; k++){
                    f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
                }
            }
        }
    }
    cout<<f[1][n]<<endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);


 

  
        solve();
    

    return 0;
}

https://vjudge.net/problem/CodeForces-1178F1/origin

可以知道对于一个区间来说,设区间最小值的位置为p,对于[l,r]这一次处理的位置一定为p,我们可以设f[l,r]代表这个区间全部为同一种颜色,那么对于p,我们可以给l<=i<=p,p<=j<=r;[i,j]这个区间涂抹上p的颜色,这时候注意到[l,i-1]和[j+1,r]的颜色是不变的,根据前面的假设可以知道,这两个区间的颜色都是一样的,于是对于[i,j]区间来说,答案为

\sumf[l,i-1]*f[i,j]*f[j+1,r](l<=i<=p,p<=j<=r);

注意到乘法表示方案数:于是当i-1<=l时,f[l,i-1]=1;

根据乘法原理我们可以拆开f[i,j]=f[i,p]*f[p+1,j];

于是合并信息处理结束,注意使用记忆化搜索

#include <bits/stdc++.h>

using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;
const int N=510,mod=998244353;
int n,m;
int a[N];
int f[N][N];
typedef long long LL;
LL dfs(LL l, LL r){
    if(f[l][r]){
        return f[l][r];
    }
    if(l>=r)return f[l][r]=1;
    int p=l;
    for(int i=l+1; i<=r;i++ ){
        if(a[i]<a[p])p=i;
    }
    LL ans1=0,ans2=0;
    for(int i=l; i<=p; i++)ans1=(ans1+(dfs(l,i-1)*dfs(i,p-1)%mod))%mod;
    for(int i=p; i<=r; i++)ans2=(ans2+(dfs(p+1,i)*dfs(i+1,r)%mod))%mod;
    return f[l][r]=ans1*ans2%mod;
    
}


void solve() {

    cin>>n>>m;
    memset(f,0,sizeof f);
    for(int i=1; i<=n; i++){
        cin>>a[i];
    }
    dfs(1,m);
    cout<<f[1][m]<<endl;
    
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);


 

  
        solve();
    

    return 0;
}

[USACO16OPEN] 262144 P - 洛谷

利用合并特点优化的一道题:

需要知道合并[l,r]区间结束只能得到一个值,不会存在最大和最小值之分,假设合并完区间后,存在最大值和最小值,那么minnum<=maxnum-1;设区间长度为n,可以知道合并完区间,增量为n-1;

假设存在某种合并方式使得n-1的增量使得合并完区间的值为minnum,那么当最大值为maxnum时,增量最少为2*(n-1),结论和增量守恒矛盾。于是合并完某个区间只能得到一个值。

代码:

#include <bits/stdc++.h>

using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;
const int N=3*1e5+10,mod=998244353;
int n,m;
int a[N];
int f[61][N];

void solve() {

    cin>>n;
    int res=0;
    for(int i=1; i<=n; i++){
        cin>>a[i];
        f[a[i]][i]=i+1;
        res=max(res,a[i]);
    }
    for(int len=2; len<=58; len++){
        for(int j=1; j<=n; j++){
            
            f[len][j]=max(f[len-1][f[len-1][j]],f[len][j]);
            
            if(f[len][j])res=len;
        }
    }
    cout<<res<<endl;
    
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);


 

  
        solve();
    

    return 0;
}

树形dp题目清单:

潜入行动 - 洛谷 P4516 - Virtual Judge

状态转移方程较多,别写错即可

树形dp常用于求树的长度,观察所有节点或者边的最小代价,最大代价;方案数。

这里题目链接被加密发不出;

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值