#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;
转换公式就为:
区间[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就是这种作用。
状态转移方程:
其他的几乎一样,就不废话了。
代码
#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;
}