专题一:DAG上的动态规划
DAG模型
嵌套矩形问题
原题: 有 n n n个矩形,每个矩形可以用两个整数 a a a, b b b描述,表示它的长和宽。矩形 X ( a , b ) X(a,b) X(a,b)可以嵌套在矩形 Y ( c , d ) Y(c,d) Y(c,d)中,当且仅当 a < c , b < d a<c,b<d a<c,b<d,或者 b < c , a < d b<c,a<d b<c,a<d(相当于把矩形 X X X旋转90度)。例如, ( 1 , 5 ) (1,5) (1,5)就可以嵌套在 ( 6 , 2 ) (6,2) (6,2)中,但不可以嵌套在 ( 3 , 4 ) (3,4) (3,4)内。你的任务是选出尽可能多的矩形排成一行,使得除了最后一个之外,每一个矩形都可以嵌套在下一个矩形内。如果有多解,矩形编号的字典序应该尽量小。
分析: 这个就是典型的DAG模型(DAG的意思为有向无环图。)。 我们还是按照动态规划的步骤去解决此类问题,那么如何确定状态,如何将这些关系联立起来,这是重点。在此问题中,矩形的“可嵌套”关系就是一个典型的二元关系,二元关系可以用图来建模。即用 G r a p h [ i ] [ j ] Graph[i][j] Graph[i][j]的值就表示第 i i i个矩形能不能嵌套在第 j j j个矩形中。这个有向图是无环的,因为一个矩形无法嵌套它自己。 所以,这就是一个DAG模型,我们这个题实际上就是在解决DAG上的最长路径。
解题思路: 我们知道了上面的分析,我们自然可以来做这道题,这题和之前的不太一样,这个题需要通过递归去实现,但我们确实是想实现记忆化搜索,所以我们想要使用到引用来实现,通过对另一个别名变量进行修改达到目的。 由于起点矩形不确定,我们的方法就是一次遍历矩形,即以遍历到的矩形为起点去开启搜索。最后取最大值即可,具体看代码(打印的话要注意输出字典序最小的路径,这个时候我们就可以通过忍贪心算法来实现,同时利用的一个重要条件就是 d [ i ] = d [ j ] + 1 d[i]=d[j]+1 d[i]=d[j]+1)。
嵌套矩形问题实现代码
/*
*邮箱:unique_powerhouse@qq.com
*blog:https://me.csdn.net/hzf0701
*注:文章若有任何问题请私信我或评论区留言,谢谢支持。
*
*/
#include<bits/stdc++.h> //POJ不支持
#define rep(i,a,n) for (int i=a;i<=n;i++)//i为循环变量,a为初始值,n为界限值,递增
#define per(i,a,n) for (int i=a;i>=n;i--)//i为循环变量, a为初始值,n为界限值,递减。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair
using namespace std;
const int inf = 0x3f3f3f3f;//无穷大
const int maxn = 1e2;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
//*******************************分割线,以上为自定义代码模板***************************************//
int n;//矩形的数量
struct Rectangle{
int length;
int width;
bool operator <(const Rectangle &a){
return (length<a.length&&width<a.width)||(length<a.width&&width<a.length);
}
};
Rectangle rectangles[maxn];
bool Graph[maxn][maxn];//有向图,DAG上的动态规划。
int d[maxn];//dp[i]表示以i为起点的最优解。
int dp(int i){
int & ans=d[i];
//利用引用特性可以实际对dp[i]进行修改。
if(ans>0) return ans;//如果它大于0,说明已经有了最优解。没有必要继续搜索
ans = 1;//算上它自己。
rep(j,1,n){
if(Graph[i][j]){
ans=max(ans,dp(j)+1);//往下继续寻找。
}
}
return ans;
}
void Print(int i){
//i为起点矩形编号。
cout<<i<<" ";
rep(j,1,n){
if(Graph[i][j]&&d[i]==d[j]+1){
Print(j);
break;
}
}
}
void solve(){
memset(d,0,sizeof(d));
rep(i,1,n){
rep(j,1,n){
if(rectangles[i]<rectangles[j]){
Graph[i][j]=true;
}
else{
Graph[i][j]=false;
}
}
}
int result=0;
rep(i,1,n){
result=max(result,dp(i));
}
cout<<"最大矩形数为:"<<result<<endl;
rep(i,1,n){
if(d[i]==result){
Print(i);//打印。
}
}
}
int main(){
//freopen("in.txt", "r", stdin);//提交的时候要注释掉
IOS;
while(cin>>n){
rep(i,1,n){
cin>>rectangles[i].length>>rectangles[i].width;
}
solve();
}
return 0;
}
硬币问题
原题: 有 n n n种硬币,面值分别为: V 1 , V 2 ⋅ ⋅ ⋅ , V n V_1,V_2···,V_n V1,V2⋅⋅⋅,Vn,每种都有无限多,给定非负整数 S S S,可以选用多少个硬币,来使得面值之和刚好为 S S S?输出硬币数目的最小值和最大值。 1 ≤ n ≤ 100 , 0 ≤ S ≤ 10000 , 1 ≤ V i ≤ S 。 1 \leq n \leq 100,0 \leq S \leq 10000,1 \leq V_i \leq S。 1≤n≤100,0≤S≤10000,1≤Vi≤S。
分析: 此题虽然看起来和嵌套矩形问题不大一样,但是本质上还是DAG上的动态规划问题。我们还是将面值看成一个点,表示还需要凑够的面值,那么初始状态我们显然可知是S,目标状态为0,我们就是在 S − > 0 S->0 S−>0这条路径上作转移。如果当前状态在 i i i,使用了一个硬币 j j j,那么状态就变为 i − j i-j i−j。
解题思路: 我们已求最大值为例(会求最大值自然也是会求最小值的。)同样,对于此题,我们先要找出动态转移方程,这易得: i f ( j ≤ i ) d [ i ] = m a x ( d [ i ] , d [ j − i ] + 1 ) if(j\leq i)d[i]=max(d[i],d[j-i]+1) if(j≤i)d[i]=max(d[i],d[j−i]+1)。我们根据这个来解决此题,可以使用递归和非递归两种方法解决此题。这两种方法我们都可以实现记忆化搜索,具体看代码。
硬币问题递归实现代码(求最大值)
/*
*邮箱:unique_powerhouse@qq.com
*blog:https://me.csdn.net/hzf0701
*注:文章若有任何问题请私信我或评论区留言,谢谢支持。
*
*/
#include<bits/stdc++.h> //POJ不支持
#define rep(i,a,n) for (int i=a;i<=n;i++)//i为循环变量,a为初始值,n为界限值,递增
#define per(i,a,n) for (int i=a;i>=n;i--)//i为循环变量, a为初始值,n为界限值,递减。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair
using namespace std;
const int inf = 0x3f3f3f3f;//无穷大
const int maxn = 1e5;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
//*******************************分割线,以上为自定义代码模板***************************************//
int t;
int n,value[maxn];
bool vis[maxn];//判断该状态是否已经确定最大值。
int result[maxn];//代表所用硬币是。
void init(){
memset(vis,false,sizeof(vis));
memset(result,0,sizeof(result));
vis[0]=true;
//确定中断点,即目标点,到这了不用递归下去。这其实也是初始点。
}
int dp(int s){
if(vis[s]){
return result[s];
}
vis[s]=true; //到了这里就可以确定这个状态的最优值了,因为接下里的这个for循环正是求解最优值。
result[s]=-inf;
rep(i,1,n){
if(s>=value[i]){
result[s]=max(result[s],dp(s-value[i])+1);
}
}
return result[s];
}
int main(){
//freopen("in.txt", "r", stdin);//提交的时候要注释掉
IOS;
int s;
while(cin>>t){
while(t--){
init();
cin>>n>>s;
rep(i,1,n){
cin>>value[i];
}
cout<<"最大方案数为:"<<dp(s)<<endl;
}
}
return 0;
}
硬币问题非递归实现代码(求最小值)
/*
*邮箱:unique_powerhouse@qq.com
*blog:https://me.csdn.net/hzf0701
*注:文章若有任何问题请私信我或评论区留言,谢谢支持。
*
*/
#include<bits/stdc++.h> //POJ不支持
#define rep(i,a,n) for (int i=a;i<=n;i++)//i为循环变量,a为初始值,n为界限值,递增
#define per(i,a,n) for (int i=a;i>=n;i--)//i为循环变量, a为初始值,n为界限值,递减。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair
using namespace std;
const int inf = 0x3f3f3f3f;//无穷大
const int maxn = 1e5;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
//*******************************分割线,以上为自定义代码模板***************************************//
int t;
int n,value[maxn];
int dp[maxn];//代表所用硬币是。
int main(){
//freopen("in.txt", "r", stdin);//提交的时候要注释掉
IOS;
int s;
while(cin>>t){
while(t--){
cin>>n>>s;
rep(i,1,n){
cin>>value[i];
}
memset(dp,inf,sizeof(dp));
dp[0]=0;
rep(i,1,s){
rep(j,1,n){
if(i-value[j]>=0){
dp[i]=min(dp[i],dp[i-value[j]]+1);
}
}
}
cout<<dp[s]<<endl;
}
}
return 0;
}
总结: 本文介绍了DAG上的动态规划问题——最长路与最短路,这是可以通过递推和记忆化搜索两种方法来实现的,找准状态列出状态转移方程则可解决此类问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!