有向无环图(DAG,Directed Acyclic Graph)上的动态规划是学习动态规划的基础。很多问题都可以转化为DAG上的最长路、最短路或路径计数问题。

一、矩形嵌套

题目描述:

       有n个矩形,每个矩形可以用两个整数a,b描述,表示它的长和宽。矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a<c,b<d,或者b<c,a<d(相当于把矩形X旋转90°)。例如(1,5)可以嵌套在(6,2)内,但不能嵌套在(3,4)内。你的任务是选出尽可能多的矩形排成一行。使得除了最后一个之外,每个矩形都可以嵌套在下一个矩形内。



解题思路:

       如何求DAG中不固定起点的最长路径呢?仿照数字三角形的做法,设d(i)表示从节点i出发的最长路长度,应该如何写状态方程呢?第一步只能走到它的相邻点,因此:

           d(i) = max { d(j) + 1 | (i,j)-> E }

其中E为边集,最终答案为d(i).那如果要求输出字典序最小的最长路径呢?那么必须找到第一个最长的路径的值然后递归输出。

代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std ;
const int MX = 1000 + 10 ;
int n ;
int G[MX][MX],dp[MX] ;
struct node
{
int x,y ;
}T[MX] ;
void buildGraph() // 建图
{
memset(G,0,sizeof(G)) ;
for(int i=0 ;i<n ;i++)
for(int j=0 ;j<n ;j++)
if(T[i].x>T[j].x&&T[i].y>T[j].y)
G[i][j]=1 ;
}
int DAG(int x) // 记忆化求解
{
int& ans = dp[x] ;
if(ans > 0) return ans ;
ans=1 ;
for(int i=0 ;i<n ;i++)
if(G[x][i])
{
int mx=DAG(i)+1 ;
ans = ans > mx ? ans : mx ;
}
return ans ;
}
void print(int x) // 打印路径
{
printf("%d ",x) ;
for(int i=0 ;i<n ;i++)
if(G[x][i]&&dp[x]==dp[i]+1)
{
print(i) ;
break ;
}
}
int main()
{
int Tx ;
scanf("%d",&Tx) ;
while(Tx--)
{
scanf("%d",&n) ;
for(int i=0 ;i<n ;i++)
{
scanf("%d%d",&T[i].x,&T[i].y) ;
if(T[i].x>T[i].y)
swap(T[i].x,T[i].y) ;
}
int ans=1 ;
buildGraph() ;
memset(dp,-1,sizeof(dp)) ;
for(int i=0 ;i<n ;i++)
{
int mx=DAG(i) ;
ans= mx > ans ? mx : ans ;
}
for(int i=0 ;i<n ;i++)// 寻找第一个点
if(dp[i]==ans)
{
printf("%d\n",ans) ;
print(i) ;
break ;
}
}
return 0 ;
}

二、硬币问题



题目描述:

        有n种硬币,面值分别为V1,V2...,Vn,每种都有无限多。给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值和最大值。0 <= n <= 100, 0 <= S <= 10000, 1 <= Vi <= S。

解题思路:

        本题的本质还是DAG上的路径问题。我们把每种面值看作一个点,表示"还需要凑足的面值",则初始状态为S,目标状态为0。若当前的状态i,每使用一个硬币j,状态便转移到i-Vj。这个模型和嵌套矩形一题类似,但也有些明显的不同之处:上题并没有确定路径的起点和终点(可以把任意矩形放在第一个和最后一个),而本题的起点必须是S,终点必须是0。把终点固定之后"最短路"才是有意义的。在嵌套矩形中,最短序列显然是空(如果不允许空的话,就是单个矩形,不管怎样都是平凡的),而本题的最短路径却不是那么容易确定的。  
          

       接下来考虑"硬币问题"。注意到最长路和最短路的求法是类似的,下面只考虑最长路。由于终点固定,d(i)的确切含义变为"从节点i出发到节点0的最长路径长度"。

代码:

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#define INF 1<<30
#define maxn 100+10
using namespace std ;
int V[maxn],n;
int min[maxn],max[maxn]; inline int Min(int a,int b){return a<b?a:b;}
inline int Max(int a,int b){return a>b?a:b;} //打印可行的方案
void print_ans(int* d,int S)
{
for(int i=1;i<=n;i++)
{
if(S>=V[i] && d[S]==d[S-V[i]]+1)
{
printf("%d ",V[i]);
print_ans(d,S-V[i]);
break;
}
}
}
int main()
{
int S;
while(~scanf("%d%d",&S,&n)) //输入面值S和面值的种数n
{
for(int i=1;i<=n;i++)
scanf("%d",&V[i]);
max[0]=0; min[0]=0;
for(int i=1;i<=S;i++)
min[i]=INF,max[i]=-INF;
//递推实现
for(int i=1;i<=S;i++)
for(int j=1;j<=n;j++)
if(i>=V[j])
{
min[i]=Min(min[i],min[i-V[j]]+1);
max[i]=Max(max[i],max[i-V[j]]+1);
}
print_ans(min,S);
printf(" min\n");
print_ans(max,S);
printf(" max\n");
printf("min:%d max:%d\n",min[S],max[S]);
}
return 0;
}

分析:本质上市一个DAG上的路径问题,我们把每种面值看做一个点,表示还需凑足的面值,则初始状态为0,目标状态为0,若当前在i,则每使用一枚硬币j,状态转移到i-vj。

代码:

#include<stdio.h>
#define N 1100
int v[N],min[N],max[N],min_coins[N],max_coins[N]; void print_ans(int *d,int s, int n) {
while(s){
printf("%d ",v[d[s]]);
s-=v[d[s]];
}
printf("\n");
}
int main() {
int T,i,j,n,s;
scanf("%d",&T);
while(T--) {
scanf("%d %d",&n,&s);
for(i=0;i<n;i++)
scanf("%d",&v[i]);
min[0]=max[0]=0;
for(i=1;i<=s;i++){
min[i]=0x7FFFFFFF;
max[i]=-0x7FFFFFFF;
}
for(i=1;i<=s;i++){
for(j=0;j<n;j++){
if(i>=v[j]){
//min[i]=min[i]<(min[i-v[j]]+1)?min[i]:(min[i-v[j]]+1);
if(min[i]>min[i-v[j]]+1){
min[i]=min[i-v[j]]+1;
min_coins[i]=j;
}
if(max[i]<max[i-v[j]]+1){
max[i]=max[i-v[j]]+1;
max_coins[i]=j;
}
//max[i]=max[i]>(max[i-v[j]]+1)?max[i]:(max[i-v[j]]+1);
}
}
}
printf("%d %d\n",min[s],max[s]);
print_ans(min_coins,s,n);
print_ans(max_coins,s,n);
}
return 0;
}

上面的代码中,如果不需要输出路径的话,则可以不要max_coins和min_coins数组,下面在写一个递归的。

#include<stdio.h>
#include<string.h>
#define N 1100 int v[N],d[N],vis[N]; int dp(int s, int n) {
int i;
if(vis[s])
return d[s];
vis[s]=1; d[s]=-1<<30;//没有算过的我们假定很小
for(i=0;i<n;i++)
if(s>=v[i]&&d[s]<dp(s-v[i],n)+1)
d[s]=dp(s-v[i],n)+1;
return d[s];
}
void print_ans(int s,int n){
int i;
for(i=0;i<n;i++){
if(s>=v[i]&&d[s]==d[s-v[i]]+1){
printf("%d ",v[i]);//输出所选的银币面值
print_ans(s-v[i],n);
break;
}
}
}
int main() {
int T,i,n,s,ans;
scanf("%d",&T);
while(T--) {
scanf("%d %d",&n,&s);
for(i=0;i<n;i++) {
scanf("%d",&v[i]);
}
memset(vis,0,sizeof(vis));
vis[0]=1;
d[0]=0;//终点状态药初始化为0,访问过
ans=dp(s,n);
printf("%d\n",ans);
print_ans(s,n);
printf("\n");
}
return 0;
}

只写了求最长路径的,如果要求最短路径,则和最长路径类似,在此省略。

DAG 上的动态规划(训练指南—大白书)的更多相关文章

  1. UVA 1025 "A Spy in the Metro " (DAG上的动态规划?? or 背包问题??)

    传送门 参考资料: [1]:算法竞赛入门经典:第九章 DAG上的动态规划 题意: Algorithm城市的地铁有 n 个站台,编号为 1~n,共有 M1+M2 辆列车驶过: 其中 M1 辆列车从 1 ...

  2. UVa 103 Stacking Boxes --- DAG上的动态规划

    UVa 103 题目大意:给定n个箱子,每个箱子有m个维度, 一个箱子可以嵌套在另一个箱子中当且仅当该箱子的所有的维度大小全部小于另一个箱子的相应维度, (注意箱子可以旋转,即箱子维度可以互换),求最 ...

  3. DAG上的动态规划之嵌套矩形

    题意描述:有n个矩形,每个矩形可以用两个整数a.b描述,表示它的长和宽, 矩形(a,b)可以嵌套在矩形(c,d)当且仅当a<c且b<d, 要求选出尽量多的矩形排成一排,使得除了最后一个外, ...

  4. 第九章(二)DAG上的动态规划

    DAG上的动态规划: 有向无环图上的动态规划是学习DP的基础,很多问题都可以转化为DAG上的最长路.最短路或路径计数问题. 1.没有明确固定起点重点的DAG模型: 嵌套矩形问题:有n个矩形,每个矩形可 ...

  5. DP入门(2)——DAG上的动态规划

    有向无环图(DAG,Directed Acyclic Graph)上的动态规划是学习动态规划的基础.很多问题都可以转化为DAG上的最长路.最短路或路径计数问题. 一.DAG模型 [嵌套矩形问题] 问题 ...

  6. 嵌套矩形——DAG上的动态规划

    有向无环图(DAG,Directed Acyclic Graph)上的动态规划是学习动态规划的基础.非常多问题都能够转化为DAG上的最长路.最短路或路径计数问题. 题目描写叙述: 有n个矩形,每一个矩 ...

  7. DAG上的动态规划---嵌套矩形(模板题)

    一.DAG的介绍 Directed Acyclic Graph,简称DAG,即有向无环图,有向说明有方向,无环表示不能直接或间接的指向自己. 摘录:有向无环图的动态规划是学习动态规划的基础,很多问题都 ...

  8. 9.2 DAG上的动态规划

    在有向无环图上的动态规划是学习动态规划的基础,很多问题都可以转化为DAG上的最长路,最短路或路径计数问题 9.2.1 DAG模型 嵌套矩形问题: 矩形之间的可嵌套关系是一种典型的二元关系,二元关系可以 ...

  9. UVA 437 The Tower of Babylon(DAG上的动态规划)

    题目大意是根据所给的有无限多个的n种立方体,求其所堆砌成的塔最大高度. 方法1,建图求解,可以把问题转化成求DAG上的最长路问题 #include <cstdio> #include &l ...

随机推荐

  1. spring cloud feign覆写默认配置级feign client的日志打印

    一.覆写fegin的默认配置 1.新增配置类FeignConfiguration.java package com.config; import org.springframework.context ...

  2. java读取.txt文件工具类FileUtiles

    public class FileUtils { private static final String ENCODING = "UTF-8";//编码方式 /** * 获取文件的 ...

  3. FCN 项目部分代码学习

    下面代码由搭档注释,保存下来用作参考. github项目地址:https://github.com/shekkizh/FCN.tensorflowfrom __future__ import prin ...

  4. python多线程爬取-今日头条的街拍数据(附源码加思路注释)

    这里用的是json+re+requests+beautifulsoup+多线程 1 import json import re from multiprocessing.pool import Poo ...

  5. 20165206 预备作业3 Linux安装及学习

    Linux的安装与学习 - 在自己笔记本上安装Linux操作系统 在安装虚拟机的过程中遇到了不少问题,但也都进行了尝试并得到了解决.首先是在安装VirtulBox的安装上,按照老师给的链接下载安装,不 ...

  6. Nginx安装配置&反向代理

    使用Nginx作为前端服务能够更快更及时的响应静态页面.js.图片等,当客户端请求访问动态页面时由Nginx的反向代理给Apache处理,Apache处理完再交予Nginx返回给客户端. Nginx更 ...

  7. Docker常见命令

    docker制作Images docker build -t 镜像名 .(“.”最后这一个点不能忽略) docker 运行Images docker run --name=容器名 --net=host ...

  8. canvas 画线

    一.canvas是基于状态的绘图环境 1.canvas是基于状态的绘制 context.moveTo(100,100); context.lineTo(700,700); context.lineWi ...

  9. zTree的使用教程

    1.首先去官网下载http://www.ztree.me/v3/main.php#_zTreeInfo 2.之后引入: <script src="js/jquery.ztree.all ...

  10. IO流-file

    1.1 IO概述 回想之前写过的程序,数据都是在内存中,一旦程序运行结束,这些数据都没有了,等下次再想使用这些数据,可是已经没有了.那怎么办呢?能不能把运算完的数据都保存下来,下次程序启动的时候,再把 ...