DAG上的动态规划问题

本文介绍了DAG(有向无环图)的概念,并通过矩形嵌套问题和硬币问题两个实例展示了DAG在动态规划中的应用。在矩形嵌套问题中,利用邻接矩阵和动态规划求解最长矩形序列;在硬币问题中,通过构建DAG寻找面值和为给定数值的硬币组合的最小和最大数量。
摘要由CSDN通过智能技术生成

累加器传送门:

http://blog.csdn.net/NOIAu/article/details/71775000

首先我们要明确一个概念,DAG是什么?

在数学和计算机科学的,有向无环图(DAG), is a directed graphwith no directed cycles. 是一个有向图,无定向的周期。That is, it is formed by a collection of vertices and directed edges, each edge connecting one vertex to another, such that there is no way to start at some vertex v and follow a sequence of edges that eventually loops back to v again. [ 1 ][ 2 ][ 3 ]也就是说,它是由集合的顶点和有向边构成,每条边连接一个顶点到另一个,这样,在一些顶点v开始,沿着有序的边,最终循环回再次到V是不可能的(摘自百度百科,(当然,百度百科可能摘自维基百科));

那么什么时候在dp中可以用到DAG图的思想呢,关于这个,我们来举几个简单的例子

No.1 矩形嵌套问题

有n个矩形,每个矩形可以用a,b来描述,表示它的长和宽,如果一个矩形的长和宽严格地小于另一个矩形的长和宽的时候,就说这个矩形可以嵌套在另一个矩形里(当然,a可以作为长,也可以作为宽),现在我们的问题是选出最多的矩形,使得他们排列出的矩形列满足该性质,请找出最长的矩形列,如果有多解,输出编号字典序最小的据阵列

一个矩形和另一个矩形的长宽关系(是否能够嵌套)是一个二元组关系,我们可以通过建图来解决这个问题,比如一个矩形可以嵌套在另一个矩形外面,我们就由这个矩形向另一个矩形连一条边,而任何一个矩形都无法直接或者间接地嵌套在自己外面,所以这是一个有向无环图无疑

首先预处理出一个矩形是否可以连接另一个矩形,用邻接矩阵来存储

预处理的代码如下

#include<cstdio>
#include<iostream>
#include<cstring>
#define MAXN 1000+10

using namespace std;

int n,a[MAXN],b[MAXN];
bool G[MAXN][MAXN];
int dp[MAXN];

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if((a[i]>a[j]&&b[i]>b[j])||(a[i]>b[j]&&b[i]>a[j]))
            G[i][j]=true;
        }
    }
}

然后是记忆化搜索或者是递推
用dp[i]来表示以i为第一个矩形或者以i为最后一个矩形的最长路长度(状态定义的不一样递推方式不一样)
设想在一个图中,如何进行dp转移呢
法一:如果从节点u可以连到节点v,那么就可以用它来进行刷表法,每到一个点,就更新它所能连到的所有点
法二:用它能连到的所有点来更新它,这里贴上法二的代码

int dp(int i){
    if(dp[i]>0) return dp[i];
    dp[i]=1;
    for(int j=1;j<=n;j++){
        if(G[i][j]) dp[i]=max(dp[i],dp[j]+1);
    }
    return dp[i];
}

如果没有到过的话,dp[i]就等于1,因为无论如何我是可以不嵌套任何矩形的,此时至少有我自己一个矩形
如果可以到的话(G[i][j])那就用j的最大长度来更新我自己,因为我还可以套在j外面,所以是dp[j]+1;

No.2 硬币问题

有n种硬币,面值分别为v1,v2,v3…vn,每种都有无限多,给定一个非负整数S。可以选用多少个硬币,使得面值之和刚好为S?输出硬币数的最大值和最小值

解析:这道题没每种值看做一个点,表示还需凑足的面值,用maxn[i]表示我需凑i块钱,现在最多已经用了maxn[i]枚硬币,minn[i]同理,则这个DAG的起点是S,终点是0,也就是需凑S块钱(此时一块硬币也没用)一直到已经凑够了S块钱

所以直接上代码

#include<cstdio>
#include<iostream> 
#define MAXN 100000
#define MINN -100000 
#define MAXNN 1000+10
using namespace std;

int S,n,v[MAXNN];
int maxn[MAXNN],minn[MAXNN];
int main(){
    scanf("%d%d",&S,&n);
    for(int i=1;i<=n;i++) scanf("%d",&v[i]);
    for(int i=1;i<=S;i++){
        maxn[i]=MAXN;
        minn[i]=MINN;
    }
    maxn[0]=minn[0]=0;
    for(int i=1;i<=S;i++){
        for(int j=1;j<=n;j++){
            if(i>=v[j]){
                maxn[i]=max(maxn[i],maxn[i-v[j]]+1);
                minn[i]=min(minn[i],minn[i-v[j]]+1);
            }
        }
    }
    cout<<maxn[S]<<endl<<minn[S];
}

注意最后输出的是maxn[S]和minn[S],表示凑S块的时候最多和最少用多少块硬币,如果题目说了可能无解的话,就可以加个判定是否等于MAXN或者MINN即可;

这就是两例DAG模型在dp上的应用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值