专题一: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。 1n100,0S10000,1ViS

分析: 此题虽然看起来和嵌套矩形问题不大一样,但是本质上还是DAG上的动态规划问题。我们还是将面值看成一个点,表示还需要凑够的面值,那么初始状态我们显然可知是S,目标状态为0,我们就是在 S − > 0 S->0 S>0这条路径上作转移。如果当前状态在 i i i,使用了一个硬币 j j j,那么状态就变为 i − j i-j ij

解题思路: 我们已求最大值为例(会求最大值自然也是会求最小值的。)同样,对于此题,我们先要找出动态转移方程,这易得: 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(ji)d[i]=max(d[i],d[ji]+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上的动态规划问题——最长路与最短路,这是可以通过递推和记忆化搜索两种方法来实现的,找准状态列出状态转移方程则可解决此类问题。

posted @   unique_pursuit  阅读(113)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示