【Kuangbin 带你飞系列】 基础dp

dp好难啊啊啊啊啊啊啊啊啊啊

HDU1024 Max Sum Plus Plus

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

题目大意就是给你一个序列从里面截出连续m段使每一段区间不相交并且和最大

思路就是:

集合表示 :我们先确定状态dp[i][j] 就是所有划分方法的和的集合

状态表示 :以第j个数结尾分为i组的最大和是多少

集合划分:对于第j个物体我们可以说它自己自成一组或者加入最后一组

1. dp[i][j - 1] + a[j],前j - 1个物体分为i组的最大值a[j]这个加在最后一组里面的最大值

2.dp[i - 1][k] + a[j] (k >= i - 1 && k < j) k个物体分成i - 1组的最大值然后再加上a[j]自己成一组

我们发现这个是n ^ 3 的复杂度而且数据这么大我们需要优化一下

对于dp[i][j-1] 因为只需要一行我们用滚动数组去存储就好了 对于dp[i - 1][k] 是一个数值用一个数存储就好了

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>

using namespace std;
const int N = 1e6 + 10;

int a[N];
int n, m;
int dp[10][10];
int d[N][2];

int main()
{
    while(scanf("%d%d",&n,&m) != EOF)
    {
        for(int i = 1; i <= m; ++ i)
            scanf("%d",&a[i]);
            
           memset(d,0,sizeof(d)); 
        // int ans = -N;
        
        /*for(int i = 1; i <= n; ++ i)
          for(int j = i; j <= m; ++ j)
          {
              for(int k = 1; k < j; ++ k)
              dp[i][j] = max(dp[i - 1][k] + a[j], dp[i][j - 1] + a[j]);
          }
           cout << dp[n][m] << endl;
           for(int i = 1; i <= n; ++ i)
            {
                for(int j = 1; j <= m; ++ j)
                cout << dp[i][j] <<" ";
                cout << endl;
            }*/
            int maxv;
        for(int i = 1; i <= n; i++)
        {
            maxv = - 0x3f3f3f3f;
            for(int j = i; j <= m; j ++)
            {
                d[j][1] = max(d[j - 1][1] + a[j],d[j - 1][0] + a[j]);
                d[j - 1][0] = maxv;
                maxv = max(maxv,d[j][1]);
            }
        }
               
          printf("%d\n",maxv);
    }
}

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

题目大意:给你几种石头的规格然后问你石头最高可以堆多高一个石头可以放在另一个石头上面当且仅当下面石头接触面的大小严格大于上面的石头的接触面

思路有点像背包问题,我们可以将石头的每一个面都拆出来,经观察可知每个石头最多被选一次,对于每个面我们可以选和不选

设状态转移方程为dp[N][2];dp[N][1]表示选,dp[N][0]表示不选,那么表示就是我要是选这个的话max(我可以接到前面哪个石头堆上去)要是不选的话就是前面的石头堆里面的最大值

状态转移要是选的话dp[N][1] = max(dp[0 ~ N - 1][1],dp[0 ~ N - 1][0]) + h(该石头高度)

要是不选的话dp[N][0] = max(dp[0~N - 1][1],dp[0~N - 1][0]);

事先要对边进行排序

#include <iostream>
#include <map>
#include <algorithm>
#include <cstring>
#include <cstdio>

#define f first
#define s second
using namespace std;
typedef pair<int,int> PII;
const int N = 30;

map <PII,int> m;
PII p[N * N];
int dp[N * N][2];
int n;
int x, y, z;
inline void chang(int &x, int &y, int &z)
{
    if(x > y) swap(x,y);
    if(y > z) swap(y,z);
    if(x > z) swap(x,z);
    if(x > y) swap(x,y);
}
int main()
{
    int T = 1;   
    while(scanf("%d",&n) && n)
    {
        int idx = 0;
        memset(dp,0,sizeof(dp));
//........................................................................
        for(int i = 0; i < n; ++ i)
        {
            cin >> x >> y >> z;
            chang(x,y,z);
            p[idx ++] = {x,y}; m[p[idx - 1]] = z;
            p[idx ++] = {y,z}; m[p[idx - 1]] = x;
            p[idx ++] = {x,z}; m[p[idx - 1]] = y;
        }
        
        sort(p,p + idx);
//.......................................................................
        // for(int i = 0; i < idx; ++ i)
        //  cout << p[i].f << " " <<p[i].s <<" " << m[p[i]] <<" " << i <<endl;
        //  cout << endl;
         
        for(int i = 0; i < idx; ++ i)
        {
            dp[i][1] = m[p[i]];
            for(int j = 0; j < i; ++ j)
            {
                  dp[i][0] = max(max(dp[j][1],dp[j][0]),dp[i][0]);
                  if(p[i].f > p[j].f && p[i].s > p[j].s)
                  dp[i][1] = max(dp[j][1] + m[p[i]],dp[i][1]);
            }
        }
//........................................................................
        // for(int i = 0; i < idx; ++ i)
        // {
        //     for(int j = 0; j < 2; ++ j)
        //       cout << dp[i][j] <<" ";
        //       cout << i << endl;
        // }
        
        printf("Case %d: maximum height = %d\n",T ++, max(dp[idx - 1][0],dp[idx - 1][1])); 
    }
    return 0;
}

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

状态压缩dp(没想到)

题目大意给你各科的完成的时间以及ddl如果你的作业不能在规定时间内完成就会扣分求最小扣多少分

有点求哈曼顿最短路的味道(走过每一个求花费最小)

由于要记录路径所以,我们不能简单的求时长我们要记录->这个状是由哪些状态转移过来的,还要存储花费的时间

定义两个结构体一个存储输入信息,另一个记录到达当前状态的罚时,以及前面的状态是谁,和当前到了哪个状态

#include <iostream>
#include <string>
#include <cstring>
#include <stack>
#include <algorithm>
using namespace std;
 
const int inf = 1 << 30;
 
struct node
{
	string name;
	int dead, cost;
} a[50];
 
struct kode
{
	int time, score, pre, now;
} dp[1 << 15];//表示当前状态的一些内容
 

第一重循环全排列所有状态

首先初始化当前状态的罚时为INF

第二重循环判断哪些位置有1,有1说明作业已经做了

如果给位置上有1,把1去掉就是你前面是由哪个状态转移过来的

计算一下转移花费的时间

最后的状态就是所有的数位都为1最后倒着输出就可以了

end = 1 << n;//全排列所有的情况数
		for (s = 1; s < end; s++)//枚举每一种状态,最后就是所有作业都写完了
		{
			dp[s].score = inf;
			for (i = n - 1; i >= 0; i--)
			{
				int tem = 1 << i;
				if (s & tem)
				{
				    //cout<<s<<"      "<<tem<<endl;
				    //这里这一步注释的可以用来解释 s & tem 的含义
					int past = s - tem;
					int st = dp[past].time + a[i].cost - a[i].dead;//前一个状态所花的时间与当前状态时间上的一些处理
					if (st<0)   st = 0;//如果小于0是不是说明没有超过截止时间?那么我们就没有扣分,所以为0
					if (st + dp[past].score<dp[s].score)//这个在循环中更新,跳出第二重循环找到的就是最优解
					{
						dp[s].score = st + dp[past].score;
						dp[s].now = i;//当前节点
						dp[s].pre = past;//前一个状态的记录
						dp[s].time = dp[past].time + a[i].cost;//时间的更新
					}
				}
			}
		}

下面是全部代码

#include <iostream>
#include <string>
#include <cstring>
#include <stack>
#include <algorithm>
using namespace std;
 
const int inf = 1 << 30;
 
struct node
{
	string name;
	int dead, cost;
} a[50];
 
struct kode
{
	int time, score, pre, now;
} dp[1 << 15];//表示当前状态的一些内容
 
int main()
{
	int t, i, j, s, n, end;
	cin >> t;
	while (t--)
	{
		memset(dp, 0, sizeof(dp));
		cin >> n;
		for (i = 0; i<n; i++)
			cin >> a[i].name >> a[i].dead >> a[i].cost;
 
		end = 1 << n;//全排列所有的情况数
		for (s = 1; s < end; s++)//枚举每一种状态,最后就是所有作业都写完了
		{
			dp[s].score = inf;
			for (i = n - 1; i >= 0; i--)
			{
				int tem = 1 << i;
				if (s & tem)
				{
				    //cout<<s<<"      "<<tem<<endl;
				    //这里这一步注释的可以用来解释 s & tem 的含义
					int past = s - tem;
					int st = dp[past].time + a[i].cost - a[i].dead;//前一个状态所花的时间与当前状态时间上的一些处理
					if (st<0)   st = 0;//如果小于0是不是说明没有超过截止时间?那么我们就没有扣分,所以为0
					if (st + dp[past].score<dp[s].score)//这个在循环中更新,跳出第二重循环找到的就是最优解
					{
						dp[s].score = st + dp[past].score;
						dp[s].now = i;//当前节点
						dp[s].pre = past;//前一个状态的记录
						dp[s].time = dp[past].time + a[i].cost;//时间的更新
					}
				}
			}
		}
		stack<int> S;
		int tem = end - 1;
		cout << dp[tem].score << endl;//输出最少扣分
		while (tem)
		{
			S.push(dp[tem].now);//利用栈先进后出的特点
			tem = dp[tem].pre;//为了迭代进行所以这样更新
		}
		while (!S.empty())
		{
			cout << a[S.top()].name << endl;
			S.pop();
		}
	}
 
	return 0;
}

逆推形dp

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <map>
#include <cstring>
#define f first
#define s second
using namespace std;
typedef long long LL;
typedef pair <int,int> PII;
const int N = 1e5 + 5;
int gg[N][15];
int dp[N][15];
//第i秒你在j这个位置能接到饼的最大值
int main()
{
    int T;
    while(scanf("%d",&T), T)
    {
        int maxv = 0;
        memset(dp,0,sizeof(dp));
        memset(gg,0,sizeof(gg));
        for(int i = 0; i < T; ++ i)
        {
            int  x, t;
            scanf("%d%d",&x,&t);
            maxv = max(maxv,t);
            x ++;//为了不处理边界情况直接变成(1 ~ 11)
            gg[t][x] ++;
        }
        
        //  dp[i][j] = max(dp[i - 1][j - 1],dp[i - 1][j + 1],dp[i - 1][j]) + mp[{j,i}]
        for(int i = maxv; i >= 0; -- i)
            for(int j = 1; j <= 11; ++ j)
            {
                dp[i][j]=max(dp[i+1][j],max(dp[i+1][j-1],dp[i+1][j+1]));
                dp[i][j] += gg[i][j];
            }
                printf("%d\n",dp[0][6]);
    }
    return 0;
}

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

动态规划,从下往上找,dp[i][2]中dp[i][0]表示第i个平台最左边到底的最短时间,dp[i][1]表示平台最右边到底的最短时间。

状态转移方程:dp[i][1]=a[i].h-a[k].h+min(dp[k][0]+a[i].x2-a[k].x1,dp[k][1]+a[k].x2-a[i].x2);//右

dp[i][0]=a[i].h-a[k].h+min(dp[k][0]+a[i].x1-a[k].x1,dp[k][1]+a[k].x2-a[i].x1); //左

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <numeric>
const int INF=0x3f3f3f;//无穷大
using namespace std;
typedef long long ll;
int T;
int N,X,Y,MAXH;
//0表示第i个平台最左边到底的最短时间
//1表示第i个平台最右边到底的最短时间
int dp[1010][2];
struct node
{
    int x1,x2,h;
};
bool cmp(node a,node b)//根据h从大到小排列
{
    return a.h>b.h;
}
node a[1010];
void LeftTime(int i)//左
{
    int k=i+1;
    while(k<N+1&&a[i].h-a[k].h<=MAXH)
    {
        if(a[i].x1>=a[k].x1&&a[i].x1<=a[k].x2)
        {
            dp[i][0]=a[i].h-a[k].h+min(dp[k][0]+a[i].x1-a[k].x1,dp[k][1]+a[k].x2-a[i].x1);
            return;
        }
        k++;
    }
   if(a[i].h-a[k].h>MAXH)//不能到达下一平台
        dp[i][0]=INF;
    else//直接落地
        dp[i][0]=a[i].h;

    return;
}
void RightTime(int i)//右
{
    int k=i+1;
    while(k<N+1&&a[i].h-a[k].h<=MAXH)
    {
        if(a[i].x2>=a[k].x1&&a[i].x2<=a[k].x2)
        {
             dp[i][1]=a[i].h-a[k].h+min(dp[k][0]+a[i].x2-a[k].x1,dp[k][1]+a[k].x2-a[i].x2);
            return;

        }
        k++;
    }
   if(a[i].h-a[k].h>MAXH)//不能到达下一平台
        dp[i][1]=INF;
    else//直接落地
        dp[i][1]=a[i].h;

    return;
}
int main()
{
    cin >> T;
    while(T--)
    {
        memset(dp,0,sizeof(dp));
        a[0].x1=-20000,a[0].x2=20000,a[0].h=0;//大地
        cin >> N>>X>>Y>>MAXH;
        a[1].x1=X,a[1].x2=X,a[1].h=Y;//初始位置
        for(int i=2; i<=N+1; i++)
        {
            cin >> a[i].x1 >> a[i].x2 >> a[i].h;
        }
        sort(a,a+N+2,cmp);
        for(int i=N; i>=0; i--)
        {
            LeftTime(i);//左
            RightTime(i);//右
        }
        int MinTime=min(dp[0][0],dp[0][1]);
        cout << MinTime <<endl;
    }
    return 0;
}

区间dp

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

题目大意:给你n个数字v(1),v(2),…,v(n-1),v(n),每次你可以取出最左端的数字或者取出最右端的数字,一共取n次取完。假设你第i次取的数字是x,你可以获得i*x的价值。你需要规划取数顺序,使获得的总价值之和最大

思路:区间dp

因为你取得顺序步一样就会产生不同的序列,对于不同的序列再分割,从子结构最优到整体最优

dp[i][j] 代表从i取到j的最大总数

dp[i][j] = max(dp[i+1][j]+a[i](n+i-j) , dp[i][j-1]+a[j](n+i-j)) 即取右边的数 取左边的数 比较哪个大

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define Mod 1000000007
#define eps 1e-6
#define ll long long
#define INF 0x3f3f3f3f
#define MEM(x, y) memset(x, y, sizeof(x))
#define Maxn 2000+10
using namespace std;
int n;
int dp[Maxn][Maxn],a[Maxn];
int main()
{
    MEM(dp,0);
    MEM(a,0);
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];
    for(int len=0;len<n;len++)//区间长度len
    {
        for(int i=0;i+len<n;i++)//固定区间左边起点
        {
            int l=i,r=i+len;//区间左、右点
            //取右边的数   取左边的数  比较哪个大
            dp[l][r]=max(dp[l+1][r]+a[l]*(n+l-r),dp[l][r-1]+a[r]*(n+l-r));
        }
    }
    cout<<dp[0][n-1]<<endl;
    return 0;

}

绝对值类型dp

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

题目大意:农夫约翰想改造一条路,原来的路的每一段海拔是A_i,修理后是B_i,花费|A_i – B_i|。我们要求修好的路是单调不升或者单调不降的。求最小花费。

思路:可以这样设计DP, d[i][j]表示第i个数变成j的最优解, 这样它转移到d[i-1][k], 其中k<=j, 这是变成上升的, 代价是abs(a[i] - j)。 但是数太大了, 又因为每个数肯定会变成这些数中的一个数会最优, 所以我们不妨将n个数先离散化一下, 这样状态就表示成d[i][j]表示第i个数变成第j小的数, 转移到d[i-1][k],其中k<=j。 但是这样还是超时了, 因为是三重循环, 又发现,每次都是取前一层的当前最小值, 所以很容易将第3层优化掉。

感觉还是比较难的qwq证明还是不会的

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int MAXN = 2333;
const int inf = 1e9;
int n;
int dp[MAXN],a[MAXN],b[MAXN],num[MAXN];
 
 
int get_ans(int *a,int *num)
{
    memset(dp,0,sizeof dp);
    for(int i = 1 ; i <= n ; ++i)
    {
        int MIN = inf;
        for(int j = 1 ; j <= n ; ++j)
        {
            MIN = min(MIN,dp[j]);
            dp[j] = MIN + abs(a[i] - num[j]);
        }
    }
    int ans = inf;
    for(int i = 1 ; i <= n ; ++i)ans = min(ans,dp[i]);
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i = 1 ; i <= n ;++i)
    {
        scanf("%d",&a[i]);
        num[i] = a[i];
        b[n-i+1] = a[i];
    }
    sort(num+1,num+1+n);
    printf("%d\n",min(get_ans(a,num),get_ans(b,num)));
    return 0;
}

持续更新------------------------------------------------------------------------------------------------

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值