HDU DP题目思路

HDU2059 龟兔赛跑

在这里插入图片描述
在这里插入图片描述

解决思路

首先思考DP[]数组在这个问题里的含义,dp[i]代表到达第i个充电站的最短时间,所以DP[0]代表起点,DP[N+1]就是最终的答案。
考虑到特殊情况,可能从起点不充电就能抵达终点,所以DP[i]数组的初值为从起点开始,不充电,到第i个加油站的时间.(需要计算没电推车的情况)
状态转移方程:
dp[i]=min(dp[i],dp[j]+x)
dp[i]本身存储着全程不充电到达第i个站的时间
x代表在第j个充电站充电然后直接开到i的时间。(中途不充电)
选择两个方案中时间小的那个

代码实现

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
	double L,C,T,VR,VT1,VT2;
	int N;
	while(~scanf("%lf %d %lf %lf %lf %lf %lf",&L,&N,&C,&T,&VR,&VT1,&VT2))
	{
		double p[121];
		double dp[121];//dp[i]代表到达第i个站的最短时间
		for(int i=1;i<=N;i++)
		{
			scanf("%lf",&p[i]);
		}
		p[0] = 0;
		p[N+1]=L;
		dp[0]=0;
		for(int i=1;i<=N+1;i++)
		{//判断从起点开始不充电的特殊情况 
		//从起点开始,不充电,到第i个加油站的时间 
			if(p[i]<=C)
			{
				dp[i] = p[i]*1.0/VT1;
			}
			else
				dp[i] = (C*1.0/VT1)+(p[i]-C)*1.0/VT2;
		}
		//此时dp[i]存储的是从起点开始,不充电,到达第i个加油站的时间
		//式子表达为dp[i]=min(dp[i],dp[j]+x,dp[j]+y])
		//x代表在第J个充电站充电然后直接开到i的时间。(中途不充电)
		//y代表在第J个充电站不充电直接裸开到i充电站的时间。
		//PS:当然这里dp[j]+y实际不必要考虑,因为你在j-1号充电站判断是不是要充电的时候已经把在J号充电站不充电的情况考虑进去了。所以加不加问题不大,思路都是正确的。加了更容易理解,如果你熟练了以后可以直接去掉。 
		for(int i=2;i<=N+1;i++)
		{
			for(int j=1;j<i;j++)
			{//在第j个站充电后,到达第i个站的时间 
				double Ti = T;
				if(p[i]-p[j] <= C)
					Ti += dp[j]+((p[i]-p[j])*1.0/VT1);
				else
					Ti += dp[j]+(C*1.0/VT1)+((p[i]-p[j]-C)*1.0/VT2);
				dp[i] = min(Ti,dp[i]);//此时dp[i]里面存着从起点出发到加油站i的时间,若选了dp[i]则代表不充电 
			}
		}
		//cout << dp[N+1] <<endl;
		float time = L/VR*1.0;
		if(dp[N+1]>time)
			printf("Good job,rabbit!\n");
		else
			printf("What a pity rabbit!\n");		
	}
}

HDU2084 数塔

问题描述

在这里插入图片描述

在这里插入图片描述

问题思路

思路一:
dp[i][j]代表从该点出发到达底层的最大值,那么边界则是最后一层,因为最后一层的每个dp[i][j]都等于它本身,dp[1][1]则为最终答案
状态转移方程:
dp[i][j] = max(dp[i+1][j],dp[i+1][j+1]) + a[i][j];
第i层第j个节点应该选取下一层它的两个分支中大的数来加上自身的值。

思路二:
dp[i][j]代表从dp[1][1]出发到达dp[i][j]的最大值,边界是第一层第一个,dp[i][n]中最大的一个则为答案
状态转移方程:
dp[i][j] = max(dp[i-1][j],dp[i-1][j+1]) + a[i][j];
第i层第j个节点应该选取上一层它的两个前驱中大的数来加上自身的值。

代码实现

此处仅给出第一种思路的代码

#include<stdio.h>
#include<math.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
int dp[110];
int a[110][110];
int main()
{
	int c;
	while(~scanf("%d",&c))
	{
		while(c--)
		{
			int n;
			scanf("%d",&n);
			for(int i=1;i<=n;i++)
			{
				for(int j=1;j<=i;j++)
					scanf("%d",&a[i][j]);
			}
			memset(dp,0,sizeof(dp));
			for(int i=1;i<=n;i++)
			{//初始化为最后一层的数 
				dp[i] = a[n][i];
			}
			for(int i=n-1;i>=1;i--)
			{
				for(int j=1;j<=i;j++)
				{//从最后一层往回推,把下一层两个分支较大的一个与当前结点相加 
					dp[j] = max(dp[j],dp[j+1]) + a[i][j]; 
				}
			}	
			printf("%d\n",dp[1]);
		}
		return 0 ;
	}
} 

最长连续子序列和

问题描述

在这里插入图片描述

解题思路

令DP[i]表示以A[i]为结尾的连续序列的最大和
那么
在这里插入图片描述
所以只有两种情况
1、序列只有一个元素,即本身
2、序列有多个连续的元素,即dp[i-1]+A[i]
那么状态转移方程为
dp[i] = max(A[i],dp[i-1]+A[i])

代码实现

#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn = 10010;
int main()
{
    int A[maxn],dp[maxn];
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&A[i]);
    }
    //边界
    dp[0] = A[0];
    for(int i=1;i<n;i++)
        dp[i] = max(A[i],dp[i-1]+A[i]);//状态转移方程
    //遍历DP数组得到答案
    int ans = -10000;
    for(int i=0;i<n;i++)
    {
        if(ans < dp[i])
            ans = dp[i];
    }
    printf("%d\n",ans);
    return 0;
}

最长不下降子序列(LIS)

问题描述

在这里插入图片描述

解题思路

用dp[i]表示以A[i]结尾的最长不下降子序列长度,这样有两种可能
1、如果存在A[j](j<i)使得A[j]<=A[i]且dp[j]+1>dp[i],那么久把A[i]加入到A[j]后面
2、如果不存在这样的A[j],则DP[i] = 1即只有一个A[i]
状态转移方程为
dp[i] = max(1,dp[j]+1)
边界为
dp[i] = 1;
初始假设每个元素自成一个序列

代码实现

#include<stdio.h>
#include<algorithm>
using namespace std;
//8 1 2 3 -9 3 9 0 11
//最长不减子序列(可以不连续)
const int maxn = 1000;
int f[maxn],dp[maxn];
//f存放数,dp存放以该点为右端点的最优值
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {//[1,n]
        scanf("%d",&f[i]);//输入数列
    }
    int ans = -1;//记录最大的dp[i];
    //然后往右计算dp[i]
    for(int i=1;i<=n;i++)
    {//[1,n]
        dp[i] = 1;//边界初始条件,假设每个元素自成一个序列
        for(int j=1;j<i;j++)
        {//用f[i]去询问之前的节点,f[i]是否能加入它们的队列 && 加入f[j]的队列后,以f[i]结尾的序列长度可以变大
            if(f[i] >= f[j] && (dp[j]+1 > dp[i]))
                dp[i] = dp[j] + 1;//更新dp[i]
        }
        ans = max(ans,dp[i]);//取得最大的dp[]
    }
    printf("%d\n",ans);
    return 0;
}

最长公共子序列(LCS)

问题描述

在这里插入图片描述

解题思路

令dp[i][j]表示字符串A的i号位和B的j号位之前的LCS长度,如dp[4][5]表示“sads”和“admin”之间的LCS长度。分为两种情况
1、若A[i] == B[j] ,则LCS增加1位,即dp[i][j] = dp[i-1][j-1] + 1
2、若A[i] != B[j],则LCS将会继承dp[i-1][j]和dp[i][j-1]中的较大值,即dp[i][j] = max(dp[i-1][j],dp[i][j-1])
状态转移方程为:
在这里插入图片描述

代码实现

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
//8 1 2 3 -9 3 9 0 11
//最长公共子序列(可以不连续)
const int maxn = 1000;
char A[maxn],B[maxn];
int dp[maxn][maxn];

//f存放数,dp存放以该点为右端点的最优值
int main()
{
    int n;
    gets(A+1);//从下标1开始读入
    gets(B+1);
    int lenA = strlen(A+1);//因为从1开始读入,长度也从+1开始
    int lenB = strlen(B+1);
    //边界
    for(int i=0;i<=lenA;i++)
        dp[i][0] = 0;
    for(int j=0;j<=lenB;j++)
        dp[0][j] = 0;
    //状态转移方程

    for(int i=1;i<=lenA;i++)
    {
        for(int j=1;j<=lenB;j++)
        {//
            if(A[i] == B[j])//dp[i-1][j-1]存放的是i,j之前最长的公共子序列
                dp[i][j] = dp[i-1][j-1] + 1;//更新dp[i][j]
            else//如果不等,则赋值为A串目前最大的长度或B串目前最大的长度
                dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
        }
    }
    printf("%d\n",dp[lenA][lenB]);
    return 0;
}

最长回文字串

问题描述

在这里插入图片描述

解题思路

令dp[i][j]表示S[i]到S[j]之间的子串是不是回文串,是为1,否则为0.这样根据S[i]是否等于S[j]分成两类:
1、若S[i] == S[j] 那么只要S[i+1] 到 S[j-1]是回文串的话,S[i] 到 S[j]就是回文串,如果S[i+1] 到 S[j-1]不是回文串,就不是
2、若S[i] != S[j]则一定不是回文子串
状态转移方程为
在这里插入图片描述
边界为
dp[i][i] = 1,dp[i][i+1] = (S[i]==S[i+1])?1:0
还有一个问题:当使用从小到大的方式枚举时,无法保证dp[i+1][j-1]已经被计算过。如:固定i=0,j从2开始枚举,列举到4时,dp[0][4] = dp[1][3],而dp[1][3]是没有被计算过的。
所以,我们需要按照字串的长度的初始位置进行枚举,即第一遍将长度为3的子串的dp值全部求出,第二遍通过第一遍的结果计算出长度为4的子串的dp值

代码实现

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
//PATZJUJZTACCBCC
//PATZJUJZTACCBCC
//最长回文子串(可以不连续)
const int maxn = 1000;
char S[maxn];
int dp[maxn][maxn];//dp[i][j] = 1代表从S[i]到S[j]是回文字串

//f存放数,dp存放以该点为右端点的最优值
int main()
{
    gets(S);
    int ans = 1;//保存最长回文串的长度
    int len = strlen(S);
    memset(dp,0,sizeof(dp));
    //边界
    for(int i=0;i<len;i++)
    {
        dp[i][i] = 1;//单个字符长度为1
        if(i < len-1)
        {//在i枚举到最后一位字符之前
            if(S[i] == S[i+1])
            {//如果有连续的相同的字符
                dp[i][i+1] = 1;//是回文串
                ans = 2;
            }
        }
    }   
    //状态转移方程
    for(int L=3;L<=len;L++)
    {//枚举字串的长度,从3开始,因为2的情况在边界已经处理了
        for(int i=0;i+L-1 < len;i++)
        {//i代表左端点,i+L-1是右端点
            int j = i+L-1;
            if(S[i] == S[j] && dp[i+1][j-1] == 1)
            {//如果两端点相同 且内部为回文串
                dp[i][j] = 1;//更新dp[i][j]
                ans = L;//更新长度
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

DAG最长路(关键路径)

问题描述

DAG最长路可以分为两类
1、不固定起点和终点,求整个DAG中的最长路径
2、固定终点,求最长路径
在这里插入图片描述

解题思路

dp[i]表示从定点i出发能获得的最长路径,这样dp数组的最大值就是答案。
dp[i]数组的求解就是遍历i出发能到达的所有顶点,找出最长的路径。

在这里插入图片描述
边界就是出度为0的顶点,它们的路径长度为0。

代码实现

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
using namespace std;
const  int inf = 100000000;
const int maxv = 1000;
//4 4 3 0 1 1 0 2 2 1 3 2 2 3 2
//邻接矩阵版。适用于点不多的情况小于1000
int n,G[maxv][maxv];//n为顶点数,maxv为最大顶点数

int dp[maxv];//dp[i]表示从i号节点出发能获得的最长路径的长度
//仅计算最长路径的长度
/*
int DP(int i)
{//计算dp数组
    if(dp[i] > 0)//如果这个dp[i]已经算过了
        return dp[i];
    for(int j=0;j<n;j++)
    {//遍历i的所有出边
        if(G[i][j] != inf)
        {//DP(j)是j号节点出发能获得的最长路径的长度
            dp[i] = max(dp[i],DP(j)+G[i][j]);
        }
    }
    return dp[i];//返回计算完毕的dp[i]
}
*/
//具体打印出最长路径
int choice[maxv];
int DP(int i)
{//计算dp数组
    if(dp[i] > 0)//如果这个dp[i]已经算过了
        return dp[i];
    for(int j=0;j<n;j++)
    {//遍历i的所有出边
        if(G[i][j] != inf)
        {//DP(j)是j号节点出发能获得的最长路径的长度
            int temp = DP(j) + G[i][j];
            if(temp > dp[i])
            {
                dp[i] = temp;//覆盖dp[i]
                choice[i] = j;//i的后继是j
            } 
        }
    }
    return dp[i];//返回计算完毕的dp[i]
}
//得到最大的dp[i]之后
void printpath(int i)
{
    printf("%d",i);
    while(choice[i] != -1)
    {//choice数组初始化为-1
        i = choice[i];//迭代
        printf("->%d",i);
    }
}
//规定了终点为T
bool vis[maxv];//定义一个访问数组,因为dp[i]>0
int DP_T(int i)
{//计算dp数组
    if(vis[i])//如果这个dp[i]已经算过了
        return dp[i];
    vis[i] = true;
    for(int j=0;j<n;j++)
    {//遍历i的所有出边
        if(G[i][j] != inf)
        {//DP(j)是j号节点出发能获得的最长路径的长度
            int temp = DP_T(j) + G[i][j];
            if(temp > dp[i])
            {
                dp[i] = temp;//覆盖dp[i]
                choice[i] = j;//i的后继是j
            } 
        }
    }
    return dp[i];//返回计算完毕的dp[i]
}
int main()
{
    int u,v,w,t,m;
    scanf("%d%d%d",&n,&m,&t);//顶点个数,边数,终点编号
    fill(G[0],G[0]+maxv*maxv,inf);//初始化图G
    memset(choice,-1,sizeof(choice));//初始化路径数组
    for(int i=0;i<m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);//输入u,v,及u->v的边权
        G[u][v] = w;
    }

    //边界
    //memset(dp,0,sizeof(dp));//(不知道终点的情况下)出度为0的点出发的路径长度为0,遇到出度不是0的点,递归求解
    fill(dp,dp+maxv,-inf);//知道终点为t的情况
    dp[t] = 0;//终点要初始化为0

    int maxlong = -1;//记录长度
    int path = -1;//记录最长路径的出发点
    for(int i=0;i<n;i++)
    {//枚举所有顶点
        int templen = DP_T(i);
        if(maxlong < templen)
        {
            maxlong = templen;
            path = i;//更新
        }
    }
    printpath(path);
    printf("\n");
    return 0;
}

总结

以下列举部分常见的动态规划模型
1.最长连续子序列和
dp[i]表示以A[i]作为结尾的连续序列的最大和
2.最长不下降子序列LIS
dp[i]表示以A[i]作为结尾的最长不下降子序列的最大和
3.最长公共子序列LCS
令dp[i][j]表示字符串A的i号位和B的j号位之前的LCS长度
4.最长回文子串
令dp[i][j]表示S[i]到S[j]之间的子串是不是回文串
5.数塔DP
令dp[i][j]表示从第i行第j个数字出发到达底层的所以路径上得到的最大和
6.DAG最长路
dp[i]表示从定点i出发能获得的最长路径
7.01背包
令dp[i][v]表示前i件物品恰好装入容量为v的背包中所获得的最大价值
8.完全背包
令dp[i][v]表示前i件物品恰好装入容量为v的背包中所获得的最大价值

TIPS

当题目与序列或字符串有关时,常常是
1、令dp[i]表示以A[i]结尾(或开头)的XXX
2、dp[i][j]表示以A[i]到A[j]间的XXX

当题目的状态是多维的时候,对其中的每一维度采取以下其中一种描述:
1、恰好为i
2、前i
这样dp数组的含义就是“令dp数组表示恰好为i(或前i)、恰好为j(或前j)…的XXX”

最后,大部分情况下可以把动态规划的问题看作一个有向无环图DAG,图中的节点就是状态,边就是状态转移方程,求解的顺序就是按照DAG的拓扑排序进行求解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: hdu 2829 Lawrence 斜率优化dp 这道题是一道经典的斜率优化dp题目,需要用到单调队列的思想。 题目大意是给定一个序列a,求出一个序列b,使得b[i]表示a[1]~a[i]中的最小值,且满足b[i] = min{b[j] + (i-j)*k},其中k为给定的常数。 我们可以将上式拆开,得到b[i] = min{b[j] - j*k} + i*k,即b[i] = i*k + min{b[j] - j*k},这个式子就是斜率优化dp的形式。 我们可以用单调队列来维护min{b[j] - j*k},具体思路如下: 1. 首先将第一个元素加入队列中。 2. 从第二个元素开始,我们需要将当前元素加入队列中,并且需要维护队列的单调性。 3. 维护单调性的方法是,我们从队列的末尾开始,将队列中所有大于当前元素的元素弹出,直到队列为空或者队列中最后一个元素小于当前元素为止。 4. 弹出元素的同时,我们需要计算它们对应的斜率,即(b[j]-j*k)/(j-i),并将这些斜率与当前元素的斜率比较,如果当前元素的斜率更小,则将当前元素加入队列中。 5. 最后队列中的第一个元素就是min{b[j] - j*k},我们将它加上i*k就得到了b[i]的值。 6. 重复以上步骤直到处理完所有元素。 具体实现可以参考下面的代码: ### 回答2: HDU 2829 Lawrence 斜率优化 DP 是一道经典的斜率优化 DP 题目,其思想是通过维护一个下凸包来优化 DP 算法。下面我们来具体分析一下这道题目。 首先,让我们看一下该题目的描述。题目给定一些木棒,要求我们将这些木棒割成一些给定长度,且要求每种长度的木棒的数量都是一样的,求最小的割枝次数。这是一个典型的背包问题,而且在此基础上还要求每种长度的木棒的数量相同,这就需要我们在状态设计上走一些弯路。 我们来看一下状态的定义。定义 $dp[i][j]$ 表示前 $i$ 个木棒中正好能割出 $j$ 根长度为 $c_i$ 的木棒的最小割枝次数。对于每个 $dp[i][j]$,我们可以分类讨论: 1. 不选当前的木棒,即 $dp[i][j]=dp[i-1][j]$; 2. 选当前的木棒,即 $dp[i][j-k]=dp[i-1][j-k]+k$,其中 $k$ 是 $j/c_i$ 的整数部分。 现在问题再次转化为我们需要在满足等量限制的情况下,求最小的割枝次数。可以看出,这是一个依赖于 $c_i$ 的限制。于是,我们可以通过斜率优化 DP 来解决这个问题。 我们来具体分析一下斜率优化 DP 算法思路。我们首先来看一下动态规划的状态转移方程 $dp[i][j]=\min\{dp[i-1][k]+x_k(i,j)\}$。可以发现,$dp[i][j]$ 的最小值只与 $dp[i-1][k]$ 和 $x_k(i,j)$ 有关。其中,$x_k(i,j)$ 表示斜率,其值为 $dp[i-1][k]-k\times c_i+j\times c_i$。 接下来,我们需要维护一个下凸包,并通过斜率进行优化。我们具体分析一下该过程。假设我们当前要计算 $dp[i][j]$。首先,我们需要找到当前点 $(i,j)$ 在凸包上的位置,即斜率最小值的位置。然后,我们根据该位置的斜率计算 $dp[i][j]$ 的值。接下来,我们需要将当前点 $(i,j)$ 加入到下凸包上。 我们在加入点的时候需要注意几点。首先,我们需要将凸包中所有斜率比当前点小的点移除,直到该点能够加入到凸包中为止。其次,我们需要判断该点是否能够加入到凸包中。如果不能加入到凸包中,则直接舍弃。最后,我们需要保证凸包中斜率是单调递增的,这就需要在加入新的点之后进行上一步操作。 以上就是该题目的解题思路。需要注意的是,斜率优化 DP 算法并不是万能的,其使用情况需要根据具体的问题情况来确定。同时,该算法中需要维护一个下凸包,可能会增加一些算法的复杂度,建议和常规 DP 算法进行对比,选择最优的算法进行解题。 ### 回答3: 斜率优化DP是一种动态规划优化算法,其主要思路是通过对状态转移方程进行变形,提高算法的时间复杂度。HDU2829 Lawrence问题可以用斜率优化DP解决。 首先,我们需要了解原问题的含义。问题描述如下:有$n$个人在数轴上,第$i$个人的位置为$A_i$,每个人可以携带一定大小的行李,第$i$个人的行李重量为$B_i$,但是每个人只能帮助没有他们重量大的人搬行李。若第$i$个人搬运了第$j$个人的行李,那么第$i$个人会累加$C_{i,j}=\left|A_i-A_j\right|\cdot B_j$的体力消耗。求$m$个人帮助每个人搬运行李的最小体力消耗。 我们可以通过斜率优化DP解决这个问题。记$f_i$为到前$i$个人的最小体力消耗,那么状态转移方程为: $$f_i=\min_{j<i}\{f_j+abs(A_i-A_j)\cdot B_i\}$$ 如果直接使用该方程,时间复杂度为$O(n^2)$,如果$n=10^4$,则需要计算$10^8$次,运算时间极长。斜率优化DP通过一些数学推导将方程变形,将时间复杂度降低到$O(n)$,大大缩短了计算时间。 通过斜率优化DP的推导式子,我们可以得到转移方程为: $$f_i=\min_{j<i}\{f_j+slope(j,i)\}$$ 其中,$slope(j,i)$表示直线$j-i$的斜率。我们可以通过如下方式来求解$slope(j,i)$: $$slope(j,i)=\frac{f_i-f_j}{A_i-A_j}-B_i-B_j$$ 如果$slope(j,i)\leq slope(j,k)$,那么$j$一定不是最优,可以直接舍去,降低计算时间。该算法的时间复杂度为$O(n)$。 综上所述,斜率优化DP是一种动态规划优化算法,可以大大缩短计算时间。在处理类似HDU2829 Lawrence问题的时候,斜率优化DP可以很好地解决问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值