DAG是有向无环图 有向无环图的动态规划是学习动态规划的基础 有很多问题都可以转换为DAG上的最长路 最短路或者路径计数问题
DAG有向无环图貌似主要分为两个类型吧 第一个是嵌套矩形问题 第二个是硬币问题
这里为什么说嵌套矩形问题是DAG最长路径问题 首先题目的意思是
有n个矩形,每个矩形可以用两个整数a、b描述,表示它的长和宽,
矩形(a,b)可以嵌套在矩形(c,d)当且仅当a<c且b<d,
要求选出尽量多的矩形排成一排,使得除了最后一个外,
每一个矩形都可以嵌套在下一个矩形内,如果有多解,矩形编号的字典序应尽量小
你可以想这是一个典型的二元关系,二元关系可以用一个图来构建
(其实二元关系就是在一个非空集合里面有个一种关系 对于集合中的任意的两个元素都可以判断这两个元素是否满足这个关系 这个就叫做二元关系了)
如果矩形X是包含再矩形Y里面那么 X元素到T元素就有有一条边。有一个可以注意的是这个图是无环的 因为不可能自己包含自己的情况。那么这个就是有向无环图。所求的就是DAG上的最长路径
这里说一下的是镶嵌矩形问题只能求DAG图的最长路径而不能求最短路径 求最短路径要把起点还有终点确定下来才有意义不然真的没有多大意义的,确定了起点还有终点就可以求最短路径以及最长路径
首先考虑没有定点的最长路径以及其字典序、
这个的就是使用了矩阵来记录边与边之间的关系 在使用递归把整个DP找出来 初始化为-1 每次找到一个值就直接记录在DP中 下一次寻找就直接调用 但是我想不懂的是为什么最小字典序就是输出第一次遇到的
#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 ;
}
下面可以使用两个数组分别计算最大值以及最小值
因为这是确定起点还有终点的最长路径以及最短路径 所以就相当于一个序 所以就想到了动态规划了 DAG
递归实现找值 然后打印第一个情况的值
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#define INF 1<<30
#define maxn 100+10
using namespace std ;
int V[maxn],n;
int min1[maxn],max1[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]);
max1[0]=0; min1[0]=0;
for(int i=1;i<=S;i++)
min1[i]=INF,max1[i]=-INF;
//递推实现
for(int i=1;i<=S;i++)
for(int j=1;j<=n;j++)
if(i>=V[j])
{
min1[i]=Min(min1[i],min1[i-V[j]]+1);
max1[i]=Max(max1[i],max1[i-V[j]]+1);
}
print_ans(min1,S);
printf(" min\n");
print_ans(max1,S);
printf(" max\n");
printf("min:%d max:%d\n",min1[S],max1[S]);
}
return 0;
}
下面还有一种方法是多用一个数组来记录该点最长或者最短路径时 上一个点是哪一个银币 但是有一个注意得是因为你要输出最小得嘛就是要输出第一个找到得嘛 那么对先得数组要做一些处理 就是min[i]>min[i-v[j]]+1 max[i]<max[i-v[j]]+1 如果是相等或就不要改变其值 保证是第一次遇到得
#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])
{
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;
}
}
}
}
printf("%d %d\n",min[s],max[s]);
print_ans(min_coins,s,n);
print_ans(max_coins,s,n);
}
return 0;
}
DAG有向无环图貌似主要分为两个类型吧 第一个是嵌套矩形问题 第二个是硬币问题
这里为什么说嵌套矩形问题是DAG最长路径问题 首先题目的意思是
有n个矩形,每个矩形可以用两个整数a、b描述,表示它的长和宽,
矩形(a,b)可以嵌套在矩形(c,d)当且仅当a<c且b<d,
要求选出尽量多的矩形排成一排,使得除了最后一个外,
每一个矩形都可以嵌套在下一个矩形内,如果有多解,矩形编号的字典序应尽量小
你可以想这是一个典型的二元关系,二元关系可以用一个图来构建
(其实二元关系就是在一个非空集合里面有个一种关系 对于集合中的任意的两个元素都可以判断这两个元素是否满足这个关系 这个就叫做二元关系了)
如果矩形X是包含再矩形Y里面那么 X元素到T元素就有有一条边。有一个可以注意的是这个图是无环的 因为不可能自己包含自己的情况。那么这个就是有向无环图。所求的就是DAG上的最长路径
这里说一下的是镶嵌矩形问题只能求DAG图的最长路径而不能求最短路径 求最短路径要把起点还有终点确定下来才有意义不然真的没有多大意义的,确定了起点还有终点就可以求最短路径以及最长路径
首先考虑没有定点的最长路径以及其字典序、
这个的就是使用了矩阵来记录边与边之间的关系 在使用递归把整个DP找出来 初始化为-1 每次找到一个值就直接记录在DP中 下一次寻找就直接调用 但是我想不懂的是为什么最小字典序就是输出第一次遇到的
#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 ;
}
下面可以使用两个数组分别计算最大值以及最小值
因为这是确定起点还有终点的最长路径以及最短路径 所以就相当于一个序 所以就想到了动态规划了 DAG
递归实现找值 然后打印第一个情况的值
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#define INF 1<<30
#define maxn 100+10
using namespace std ;
int V[maxn],n;
int min1[maxn],max1[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]);
max1[0]=0; min1[0]=0;
for(int i=1;i<=S;i++)
min1[i]=INF,max1[i]=-INF;
//递推实现
for(int i=1;i<=S;i++)
for(int j=1;j<=n;j++)
if(i>=V[j])
{
min1[i]=Min(min1[i],min1[i-V[j]]+1);
max1[i]=Max(max1[i],max1[i-V[j]]+1);
}
print_ans(min1,S);
printf(" min\n");
print_ans(max1,S);
printf(" max\n");
printf("min:%d max:%d\n",min1[S],max1[S]);
}
return 0;
}
下面还有一种方法是多用一个数组来记录该点最长或者最短路径时 上一个点是哪一个银币 但是有一个注意得是因为你要输出最小得嘛就是要输出第一个找到得嘛 那么对先得数组要做一些处理 就是min[i]>min[i-v[j]]+1 max[i]<max[i-v[j]]+1 如果是相等或就不要改变其值 保证是第一次遇到得
#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])
{
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;
}
}
}
}
printf("%d %d\n",min[s],max[s]);
print_ans(min_coins,s,n);
print_ans(max_coins,s,n);
}
return 0;
}