kuangbin套题 基础dp1
原先分配任务时dp并非我负责,但是最近比赛表明数论选手的压力太大,于是决定开坑从零自学dp。
听缪神说kuangbin套题能很快提升水平,那不多说,开搞吧。
链接: kuangbin带你飞 专题12 基础dp1.
dp
Dynamic Programming,动态规划。用于决策问题,在贪心无法贪到最优解的情况下,如果能找到一种无后效性的决策方法,就可以尝试用dp进行求解。
某个方面来说dp和暴力其实有异曲同工之处。
A - Max Sum Plus Plus
多组数据,输入直到文件结束。每一行有一个n和一个m,以及由n的数字组成的序列。要求从其中挑出m段连续且不重复的序列,使他们的和最大。
转移方程
dp[i][j]=max(dp[i][j-1],max(dp[i-1][k]))+a[j] (i-1 <= k <= j-1)
转移方程不是很难写,但是内存卡得有点难受。
用Max把max (dp[i-1][k])记录下来,然后二维滚一维。
代码:
#include "bits/stdc++.h"
using namespace std;
int dp[100005];
int Max[100005];
//dp[i][j]=max(dp[i][j-1],max(dp[i-1][k]))+a[j] (i-1 <= k <= j-1)
int main(){
//freopen("std1.in","r",stdin);
//freopen("std1.out","w",stdout);
int n,m;
while(~scanf("%d%d",&m,&n)){
int ans=-0x3f3f3f3f;
for(int i=0;i<=m;i++)
Max[i]=-0x3f3f3f3f,dp[i]=-0x3f3f3f3f;
Max[0]=0;
for(int i=1;i<=n;i++){
int tmp;
scanf("%d",&tmp);
for(int j=m;j>=1;j--){
dp[j]=max(dp[j],Max[j-1])+tmp;
Max[j]=max(Max[j],dp[j]);
}
ans=max(ans,dp[m]);
}
printf("%d\n",ans);
}
}
B - Ignatius and the Princess IV
找出一串数字中出现次数过半的数字。
应该会打代码的都会做吧,直接跳过。
C - Monkey and Banana
给与一个n,再给予n种积木,每种积木都有长宽高三个数值, 允许旋转木块,将木块搭高,要求下方的木块的长宽严格大于上放的木块,问最高高度是多少?
首先将一个木块的旋转结果拆成6种无法旋转的木块,这样就不需要考虑木块的旋转了,然后对宽度(高度)进行排序,这样排在前面的木块定不会放在之后的木块之下,满足了无后效性。
转移方程: dp[i]=max(dp[i],dp[j]+h[i]);
代码:
#include "bits/stdc++.h"
using namespace std;
const int inf =0x3f3f3f3f;
typedef struct {
int x,y,z;
}BLOCK;
vector<BLOCK> d;
int dp[1200];
int main(){
int n,tot=0;
while(scanf("%d",&n),n){
tot++;
memset(dp,0,sizeof(dp));
d.clear();
d.push_back({inf,inf,0});
for(int i=1;i<=n;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
d.push_back({a,b,c});
d.push_back({a,c,b});
d.push_back({b,a,c});
d.push_back({b,c,a});
d.push_back({c,a,b});
d.push_back({c,b,a});
}
sort(d.begin(),d.end(),[](BLOCK a,BLOCK b){
if(a.x!=b.x)
return a.x>=b.x;
else
return a.y>=b.y;
});
int nn=6*n,ans=0;
for(int i=1;i<=nn;i++){
dp[i]=d[i].z;
for(int j=1;j<i;j++){
if(d[i].x<d[j].x&&d[i].y<d[j].y){
dp[i]=max(dp[i],dp[j]+d[i].z);
ans=max(ans,dp[i]);
}
}
}
printf("Case %d: maximum height = %d\n",tot,ans);
}
}
D - Doing Homework (状态压缩)
给一个n(n<15),之后给n个作业,每个作业有一个截止时间和一个完成时间,每次交作业如果超过了截止时间,会扣(提交时间-截止时间)的分数,问按什么顺序做扣分最少?
把作业是否完成的状态用二进制表示,完成用1未完成用0,那么可以用一个不大于2^15的数字表示所有的作业状态。
如果状态从小跑到大,那么跑每一个状态之前一定会跑过所有的可能到这个情况的状态。那么只要枚举每一个为1的位置,去其中的最小值就是当前情况的最优解。
转移方程:
dp[i]=max(dp[i],dp[i-1<<j]+time)
time = dp[i-1<<j].time + work[j].t - work[j].d;
j=1,2,3,…,n && (i & 1<<j) ==1
代码:
#include "bits/stdc++.h"
using namespace std;
const int inf =0x3f3f3f3f;
struct {
string s;
int d,t;
}work[20];
struct {
int time,from,val;
}dp[1<<16];
int main() {
int tot;
scanf("%d",&tot);
while(tot--){
int n; scanf("%d",&n);
for(int i=1;i<=(1<<n)-1;i++)
dp[i].val=0x3f3f3f3f;
for(int i=0;i<n;i++){
char tmp[150];
int a,b;
scanf("%s%d%d",tmp,&a,&b);
work[i]={tmp,a,b};
}
for(int i=0;i<= (1<<n)-1;i++) {
for (int j = n-1; j >= 0; j--) {
if (i & (1 << j)) {
int t = i - (1 << j);
int time = dp[t].time + work[j].t - work[j].d;
if (max(time,0) + dp[t].val < dp[i].val) {
dp[i].val = max(time,0) + dp[t].val;
dp[i].time = dp[t].time+work[j].t;
dp[i].from = j;
}
}
}
}
stack<int> s;
int t=(1<<n)-1;
while(t){
s.push(dp[t].from);
t-=(1<<dp[t].from);
}
printf("%d\n",dp[(1<<n)-1].val);
while(!s.empty()){
printf("%s\n",work[s.top()].s.data());
s.pop();
}
}
}
E - Super Jumping! Jumping! Jumping!
给一个n和有n的数字组成的数列,要求从左往右选择,每次选择的数字严格大于上次选择的数字,问如何选择使选择的数字和最大。
基本线性dp,只需要两个循环即可。因为每个数字都可以是第一个被选择的数字,所以只需要在开头额外放一个数值为0的点即可。
转移方程:
dp[i]=max(dp[i],dp[j]+in[i])
in[j]<in[i]
代码:
#include "bits/stdc++.h"
using namespace std;
int dp[2000];
int in[2000];
int main(){
int n;
while(scanf("%d",&n),n){
memset(dp,0,sizeof(dp));
int ans=0;in[0]=-0x3f3f3f3f;
for(int i=1;i<=n;i++){
scanf("%d",&