您的位置:首页 > 其它

动态规划--DAG(有向无环图)问题

2018-01-21 16:04 134 查看

1、矩形嵌套问题

问题描述:

有n个矩形,每个矩形可以用两个整数a、b描述,表示它的长和宽,矩形X(a,b)可以嵌套在矩形Y(c,d)中,当且仅当a<c,b<d,或者b<c,a<d。现在的任务是选出尽量多的矩形排成一排,使得除了最后一个之外,每个矩形都可以嵌套在下一个矩形中。。。如果有多解,矩形编号的字典序应尽量小。
分析:
矩形之间的可嵌套关系是一个典型的二元关系,二元关系可以用图来建模。如果矩形X可以嵌套在矩形Y中,就从X到Y连一条有向边。这个有向图是无环的,因为一个矩形无法直接或间接的嵌套在自己的内部。换句话说,它是一个DAG,我们要求的便是DAG上的最长路径。状态转移方程如下:其中E代表边集,d(i)表示从节点i出发的最长路长度,状态转移方程的第一步是找到它的相邻节点。

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

解决方法:
代码如下:首先用邻接矩阵把图保存在矩阵G中(在编写主程序之前需测试和调试程序,以确保建图过程正确无误),接下来编写记忆化搜索程序,调用前初始化数组d的所有值为0.

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

原题还有一个要求,如果有多组最优解,矩形编号的字典序应最小。
将所有的d值计算出来之后,选择最大的d[i]对应的i,如果有多个i,则选择最小的i,这样才能保证字典序最小,接下来选择d(i)=d(j)+1的j,为了让字典序最小,应选择其中最小的j,程序如下:

void print_ans(int i)
{
printf("%d",i);
for(int j=1;j<=n;j++)
if(G[i][j]&&d[i]==d[j]+1)
{
print_ans(j);
break;
}
}


2、硬币问题

问题描述:

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

这个问题和嵌套问题的共同点是:本质上也是DAG上的路径问题,将每种面值看作一个点,表示“还需要凑足的面值”,若当前的状态是i,每使用一个硬币j,状态便转移到 i-Vj。不同点是:由于可以把任意矩形放在第一个和最后一个,所以矩形嵌套问题并没有确定路径的起点和终点。而硬币问题的起点是S,终点是0。在上题中,最短序列显然是空,而本题的最短路却不容易确定。 
解决方法:

记忆化搜索:

在记忆化搜索中,用数组vis[i]表示状态 i 是否被访问过,以占用一些内存的代价来增程序的可读性。访问数组初始化为memset(vis,0,sizeof(vis))。

int dp(int S)
{
if(vis[S])
return d[S];
vis[S]=1;
int &ans=d[S];
ans=-(1<<30);
for(int i=1;i<=n;i++)
if(S>=V[i])
ans=max(ans,dp(S-V[i])+1)
return ans;
}

递推:
本题要求最小和最大值,记忆化搜索就必须写两个,在这种情况下,用递推更加方便。

minv[0]=maxv[0]=0;
for(int i=0;i<=S;i++)
{
minv[i]=INF;
maxv[i]=-INF;
}
for(int i=1;i<=S;i++)
{
for(int j=1;j<=n;j++)
if(i>=V[j])
{
minv[i]=min(minv[i],minv[i-V[j]]+1);
maxv[i]=max(maxv(maxv[i],maxv[i-V[j]]+1)
}
}

打印:

输出字典序最小的方案的方法和矩形嵌套类似。

void print_ans(int *d,int S)
{
for(int i=1;i<=n;i++)
if(S>=V[i]&&d[S]==d[S-V[i])
{
printf("%d",i);
print_ans(d,S-V[i]);
break;
}
}

然后分别调用print_ans(min,S)和print_ans(max,S)即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息