区间DP专栏 第一章(双色马、神医胡青牛、Deque等)

#A.神医胡青牛

题目描述

胡青牛是“倚天屠龙记”中的神医(但从此题目看出很贪财),每天都有N多(N<=2000)的人来求他治病,这些人排成一队,从1开始编号直到N
,每个人手里都拿着一个牌子,其上的值用Ai(1<=i<=N,1<=ai<=1000)代表,表示自己愿意付给胡大牛多少钱做为酬
金。胡神医每次从队首或队尾取一个人出来,治完这个人之后,他将获得M*Ai的Money其中M代表这个病人是第几个
被救治的。 

输入格式

如题

输出格式

如题

样例

输入数据 1
5
1
3
1
5
2

Copy

输出数据 1
43
Hint
胡大牛将按1, 5, 2, 3, 4的顺序来治人,赚得1x1 + 2x2 + 3x3 + 4x1 + 5x5 = 43.

 解题思路

(虽然这黑心🖤医生很贪财,可我们还是要帮助他……)

DP的关键在于推出状态转移方程,至于后面就很简单了。

我们先假设只有一名病人,那么得到的黑心钱就只有a1*1。

如果有两个病人,那么得到的就是a1*1+a2*2或a2*1+a1*2.

我们用dp[i][j]表示区间[i,j]可以最多得到的钱( $ _ $ ),那么每次从队头或队尾取数,就是两种情况。

1、从队头取数

dp[i][j]=dp[i+1][j]+a[i]*day;//day为病人是第几个

2、从队尾取数

dp[i][j]=dp[i][j-1]+a[j]*day;

转换公式就为:f_{i,j}=max(f_{i+1,j}+a_i*day,f_{i,j-1}+a_i*day)

区间[i,j]得到的最大钱数就是区间[i,j-1]+a[j]*day,相当于加上了队尾的钱,而区间[i+1,j]+a[i]*day,就是加上了队头的钱那么,对于两种情况,黑心的医生肯定选更多的,用max判断一下。

转换公式推出来了,那其他就so easy了!(不会有人问循环怎么写吧~,好吧讲一下)

第一层循环控制区间长度,我们设为len,就是从1~n;第二层循环是左指针,设为i;这时候我们要提前求出day,day的长度为n-j+i,对两端取数都适用。

再将刚才的状态转移方程一套,完事。

代码

#include<bits/stdc++.h>
#define  rep(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
long long n,a[3005],dp[3005][3005],sum[3005];
int main()
{
    scanf("%d",&n);
    rep(i,1,n)scanf("%d",&a[i]),dp[i][i]=0;//初始化
    rep(len,1,n){
        rep(i,1,n-len+1){
            int j=i+len-1,day=n-len+1;
            dp[i][j]=max(a[i]*day+dp[i+1][j],a[j]*day+dp[i][j-1]);//状态转移方程
        }
    }
    cout<<dp[1][n];
	return 0;
}

#B.Deque

题面翻译

给一个双端队列,双方轮流取数,每一次能且只能从队头或队尾取数,取完数后将这个数从队列中弹出。双方都希望自己取的所有数之和尽量大,且双方都以最优策略行动,假设先手取的所有数之和为 𝑋,后手取的所有数之和为 𝑌,求 𝑋−𝑌。

样例 #1

样例输入 #1
4
10 80 90 30

Copy

样例输出 #1
10

Copy

样例 #2

样例输入 #2
3
10 100 10

Copy

样例输出 #2
-80

Copy

样例 #3

样例输入 #3
1
10

Copy

样例输出 #3
10

Copy

样例 #4

样例输入 #4
10
1000000000 1 1000000000 1 1000000000 1 1000000000 1 1000000000 1

Copy

样例输出 #4
4999999995

Copy

样例 #5

样例输入 #5
6
4 2 9 7 1 5

Copy

样例输出 #5
2

Copy

提示

制約
  • 1 ≤ 𝑁 ≤ 3000
  • 1 ≤ 𝑎𝑖 ≤ 109

 解题思路

这道题和上一题有异曲同工之妙,我们就不重复了,只说下博弈论,博弈论简单来说就是在选择面前选最优,其实也是废话,max就是这种作用。

状态转移方程:dp_{i,j}=max(a_i-dp_{i+1,j},a_j-dp_{i,j-1})

其他的几乎一样,就不废话了。

代码

#include<bits/stdc++.h>
#define  rep(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
long long n,a[3005],dp[3005][3005],sum[3005];
int main()
{
    scanf("%d",&n);
    rep(i,1,n)scanf("%d",&a[i]),dp[i][i]=0;
    rep(len,1,n){
        rep(i,1,n-len+1){
            int j=i+len-1;
            dp[i][j]=max(a[i]-dp[i+1][j],a[j]-dp[i][j-1]);
        }
    }
    cout<<dp[1][n];
	return 0;
}

#C. 双色马

Description

小兵HandsomeG最近养了一群小马。

每天,HandsomeG把他的小马带到草地上,让小马们活动。小马们活动完后,就该排队回家了?。小马们必须一个一个按排队的顺序进入连续的m个马房,排在第一位的小马只能进入第1个马房,以后每只马要么和前一只马进入同一个马房,要么进入下一个马房。HandsomeG不希望浪费空间,所以他要求每个马房至少要有一只小马。

于是就有很多种分配小马的方案。但是却出现了一个问题:HandsomeG养的小马分两种——小黑马和小白马,小黑马和小白马以前很要好的呢,现在闹矛盾了?。如果同一个房间有a个小黑马,b个小白马,那么这个房间的矛盾值就为a*b,所有房间的矛盾值累计起来,如果太大,马马就会造反!

HandsomeG希望得到一种分配方案,使得矛盾值之和最小。

Format

Input

第一行两个数n,m,表示小马的数目和房间数目。 第二行n个数,0或者1,是对小马排队情况的描述;0表示小白马,1表示小黑马

Output

输出一个整数,即最小的矛盾值之和

Samples

输入数据 1

3 2
1 0 1

Copy

输出数据 1

1

 解题思路

到了这道题,后面难度就比较高了,建议先搞懂前两题,在来做后面的题

我们要求马的最小矛盾值,相信为了解决造反马马的问题,大家费了不少功夫。

我们先想一下状态转移方程,这里多出了条件——马棚数量,可以有效减少矛盾值,根据前两题经验,有左指针、右指针、加上马棚数量,一共要开三维。

到这你就happy啦?小心你数组BOOM(爆炸!)

怎么办?

我们发现,我们不能把某截造反马马分开,只能按顺序来(你想到了什么?)

这样跟右指针就没啥关系了,反正都是0。我们就可以省下一维的空间,就不会爆空间了。

怎么实现呢?

推一下状态转移方程:

1、假设只有一个牛棚,只能全塞里面(管你造不造反)

2、假设有两个牛棚,可以把一部分塞里面,另一部分塞第二个里面。(dp[2][j]=dp[1][1~j-1]+另一部分的矛盾值。)

看到这里应该都能推出来了

dp[i][j]=max(dp[i][j],dp[i-1][k]+另一部分矛盾值)

矛盾值我们可以用前缀和来求。

在输入时,我们分别用两个数组来存白与黑马的数量前缀和,另一部分矛盾值为白前缀和*黑前缀和,记得还要初始化只有一个牛棚的时候。

    rep(i,1,n){
    	scanf("%d",&t);
    	black[i]=black[i-1];
    	write[i]=write[i-1];
    	if(t)black[i]++;
    	else write[i]++;
    	dp[1][i]=black[i]*write[i];
	}

循环前两层与之前两题差不多,第三层特别讲一下,第三层控制区间断点,选择将哪一部分另存一个马棚,套起来这样写。

	rep(i,2,m){
		rep(j,1,n){
			rep(k,i-1,j){
				dp[i][j]=min(dp[i][j],dp[i-1][k]+(black[j]-black[k])*(write[j]-write[k]));
			}
		}
	}

这样基本就写完了。

代码

#include<bits/stdc++.h>
#define  rep(i,a,b) for(int i=a;i<=b;++i)//宏定义
using namespace std;
int n,m,write[1005],dp[1005][1005],black[1005],t;
int main()
{
    scanf("%d%d",&n,&m);
    memset(dp,0x3f,sizeof dp);//初始化,由于求最小值,所以设为0x3f。
    rep(i,1,n){
    	scanf("%d",&t);
    	black[i]=black[i-1];
    	write[i]=write[i-1];
    	if(t)black[i]++;
    	else write[i]++;
    	dp[1][i]=black[i]*write[i];
	}
	rep(i,2,m){
		rep(j,1,n){
			rep(k,i-1,j){
				dp[i][j]=min(dp[i][j],dp[i-1][k]+(black[j]-black[k])*(write[j]-write[k]));
			}
		}
	}
	printf("%d",dp[m][n]);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值