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",&in[i]);
for(int j=0;j<i;j++){
if(in[i]>in[j]){
dp[i]=max(dp[i],dp[j]+in[i]);
if(dp[i]>ans)
ans=dp[i];
}
}
}
printf("%d\n",ans);
}
}
F - Piggy-Bank (背包问题)
先给一个T表示T组数据,每一个组数据开头一个m1,m2,表示钱的重量和(猪(存钱罐)和钱)的重量的重量(其实减一下就行了),然后给一个n表示有n种钱,接下来n行表示这种钱的面额和这种钱的重量。问面额最小是多少,不可能则输出Impossible.
完全背包问题(该死,背包也好久没写了)。
空间开二维dp[i][j],i表示处于取第i种钱币的情况,j表示当前使用的空间容量。
二维转一维滚空间后的转移方程:
dp[i]=min(dp[i],dp[i-ob[j].h]+ob[j].m);
代码:
//define int long long 纯粹是懒人写法
#include "bits/stdc++.h"
using namespace std;
#define int long long
int dp[100050];
struct OB{
int m,h;//money,heavy
}ob[5050];
signed main(){
int tot;
scanf("%lld",&tot);
while(tot--){
int a,b,h,n;
scanf("%lld%lld",&a,&b);
h=b-a;
scanf("%lld",&n);
for(int i=1;i<=h;i++)dp[i]=0x3f3f3f3f3f3f3f3f;
dp[0]=0;
for(int i=1;i<=n;i++){
scanf("%lld%lld",&a,&b);
ob[i]={a,b};
}
for(int i=1;i<=h;i++){
for(int j=1;j<=n;j++){
if(i-ob[j].h>=0){
dp[i]=min(dp[i],dp[i-ob[j].h]+ob[j].m);
}
}
}
if(dp[h]!=0x3f3f3f3f3f3f3f3f){
printf("The minimum amount of money in the piggy-bank is %lld.\n",dp[h]);
}else
printf("This is impossible.\n");
}
}
G - 免费馅饼 (数塔问题)
中文题面就不放题意了
开一个二维空间,dp[i][j],i表示时间,j表示位置,在所有有饼的位置将dp[i][j]++,然后从i=100000跑到i=0,最后dp[0][5]就是答案。(如果把每一个位置都右移一位,就不需要考虑0越界的问题)
转移方程:
dp[i][j]=dp[i][j]+max(dp[i-1][j-1],dp[i-1][j],dp[i-1][j+1]);
代码:
#include <bits/stdc++.h>
using namespace std;
int dp[100500][20];
int main(){
int n;
while(scanf("%d",&n),n){
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
int a,b;
scanf("%d%d",&a,&b);
dp[b][a+1]++;
}
int ans=0;
for(int i=100000;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];
}
}
printf("%d\n",dp[0][6]);
}
}
H - Tickets
先给一个T表示T组数据,然后给一个n表示人数,接下来一行有n个数字,表示给n个人单独买票的时间,下一行是n-1个数字表示相邻的两个人一起买票的时间。问这n个人怎么样最快买完票。
基本线性dp.时间算过了,到不了第二天,那有手就行。
转移方程:
dp[i]=max(dp[i-1]+s[i],dp[i-2]+d[i-1]);
代码:
#include <bits/stdc++.h>
using namespace std;
int s[2500],d[2500];//single double
int dp[5000];
int main(){
int tot;
scanf("%d",&tot);
while(tot--){
int n;
memset(dp,0,sizeof(dp));
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&s[i]);
}
dp[1]=s[1];
for(int i=1;i<n;i++){
scanf("%d",&d[i]);
}
for(int i=2;i<=n;i++){
dp[i]=min(dp[i-1]+s[i],dp[i-2]+d[i-1]);
}
int ts=dp[n]%60,fs=(dp[n]/60)%60,fh=8+dp[n]/60/60 ;
bool flag=true;
if(fh>=12){
flag=false;
fh-=12;
}
printf("%02d:%02d:%02d ",fh,fs,ts);
if(flag)
printf("am\n");
else
printf("pm\n");
}
}
I - 最少拦截系统
emm这题我也不知道算不算个dp。。。找个拦截策略。。。
每次都拿已有系统中从低到高的去尝试拦截,拦不住加系统
转移方程:我也不知道是啥。
代码:
#include <bits/stdc++.h>
using namespace std;
vector<int> ans;
int main(){
int n;
while(~scanf("%d",&n)){
ans.clear();
int tmp,cmt=0;
scanf("%d",&tmp);
ans.push_back(tmp);
for(int i=2;i<=n;i++){
scanf("%d",&tmp);
int k=-1,MAX=0x3f3f3f3f;
for(int j=0;j<ans.size();j++){
if(ans[j]>=tmp&&ans[j]<MAX){
MAX=ans[j];
k=j;
}
}
if(k==-1)
ans.push_back(tmp);
else{
ans[k]=tmp;
}
}
printf("%d\n",ans.size());
}
}
J - FatMouse’s Speed
输入直到文件结束,一行给两个值,一个是老鼠的体重,一个是老鼠的速度,要求选择其中的数个,使体重严格上升,速度严格上升,问怎么选选的最多?
排序,简单线性dp
代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
typedef pair<pair<int,int>,int> piii;
vector<pair<pair<int,int>,int> > in;
typedef pair<int,int> pii;
#define MAXN 500000
int dp[MAXN];
int sel[MAXN];
stack<int> out;
int main(){
int a,b;
int id=0,tot;
while(~scanf("%d%d",&a,&b)){
id++;
in.push_back({{a,b},id});
}
for(int i=0;i<id;i++)sel[i]=-1;
sort(in.begin(),in.end(),[](piii a,piii b){
if(a.first.first!=b.first.first){
return a.first.first<b.first.first;
}else
return a.first.second>=b.first.second;
});
for(int i=0;i<id;i++){
for(int j=0;j<i;j++){
if(in[j].first.first<in[i].first.first&&in[j].first.second>in[i].first.second&& (dp[j]+1>dp[i]))
dp[i]=max(dp[i],dp[j]+1),sel[i]=j;
}
}
int ans=-1,ma=-0x3f3f3f3;
for(int i=0;i<id;i++){
if(dp[i]>ma)
ma=dp[i],ans=i;
}
while(ans!=-1){
out.push(in[ans].second);
ans=sel[ans];
}
printf("%d\n",out.size());
while(!out.empty()){
printf("%d\n",out.top());
out.pop();
}
}
K
假装自己写出来了
L - Common Subsequence (LCS)
最长公共子板子题吧
dp[i][j],i表示在第一个字符串的位置,j表示在第一个字符串的位置
转移方程
d
p
[
i
]
[
j
]
=
m
a
x
{
d
p
[
i
−
1
]
[
j
]
d
p
[
i
]
[
j
−
1
]
d
p
[
i
−
1
]
[
j
−
1
]
+
1
s
1
[
i
]
=
=
s
2
[
j
]
dp[i][j]=max\left\{ \begin{array}{rcl} dp[i-1][j] \\ dp[i][j-1] \\ dp[i-1][j-1] + 1 & & {s1[i]==s2[j]} \end{array} \right.
dp[i][j]=max⎩⎨⎧dp[i−1][j]dp[i][j−1]dp[i−1][j−1]+1s1[i]==s2[j]
代码:
#include "stdio.h"
#include "cstring"
#include "algorithm"
using namespace std;
#define MAXN 1000
char s1[MAXN],s2[MAXN];
int dp[MAXN][MAXN];
int main(){
s1[0]='k',s2[0]='k';
while(~scanf("%s%s",s1+1,s2+1)){
memset(dp,0,sizeof(dp));
int len1=strlen(s1)-1,len2=strlen(s2)-1;
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if(s1[i]==s2[j])
dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
}
}
printf("%d\n",dp[len1][len2]);
}
}
M - Help Jimmy
中文题面题意不解释了
还是简单线性dp吧,除了繁一点没啥,dp[i][j],i代表第i个板子,j=1是从左边落下,j=2是从右边落下,末尾特判。唯一要留心的地方是上一块板子会被下一块挡住。
转移方程:
dp[i][1]=min(dp[i][1],dp[j][1]+t1);
(t1表示从第j块左端到第i块左端,以下类推)
dp[i][2]=min(dp[i][2],dp[j][1]+t2);
dp[i][1]=min(dp[i][1],dp[j][2]+t1);
dp[i][2]=min(dp[i][2],dp[j][2]+t2);
代码:
#include "stdio.h"
#include "cstring"
#include "algorithm"
using namespace std;
#define MAXN 10050
struct BOAD{
int x1,x2,y;
}in[MAXN];
bool cmp(BOAD a,BOAD b){
return a.y>b.y;
}
const int inf = 0x3f3f3f3f;
int dp[MAXN][3];//左 右
bool flag[MAXN][3] ;
int main(){
int tot;
scanf("%d",&tot);
while(tot--){
int n,x,y,MAX;
scanf("%d%d%d%d",&n,&x,&y,&MAX);
memset(flag,0,sizeof(flag));
for(int i=1;i<=n+1;i++)
dp[i][1]=dp[i][2]=0x3f3f3f3f;
dp[0][1]=dp[0][2]=0;
for(int i=1;i<=n;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
in[i]={a,b,c};
}
sort(in+1,in+1+n,cmp);
n++;
in[0]={x,x,y};
in[n]={-inf,0x3f3f3f3f,0};
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
int t=-in[i].y+in[j].y;
if(t<=0||t>MAX)
continue;
if(in[j].x1>=in[i].x1&&in[j].x1<=in[i].x2&&!flag[j][1]){
flag[j][1]=true;
int t1=t,t2=t;
if(i!=n){
t1+=in[j].x1-in[i].x1;
t2+=in[i].x2-in[j].x1;
}
dp[i][1]=min(dp[i][1],dp[j][1]+t1);
dp[i][2]=min(dp[i][2],dp[j][1]+t2);
}
if(in[j].x2>=in[i].x1&&in[j].x2<=in[i].x2&&!flag[j][2]){
flag[j][2]=true;
int t1=t,t2=t;
if(i!=n){
t1+=in[j].x2-in[i].x1;
t2+=in[i].x2-in[j].x2;
}
dp[i][1]=min(dp[i][1],dp[j][2]+t1);
dp[i][2]=min(dp[i][2],dp[j][2]+t2);
}
}
}
printf("%d\n",dp[n][1]);
}
}
N - Longest Ordered Subsequence(LIS)
最长上升子板子题,没啥好说的,可以优化,但是我懒
代码:
#include<stdio.h>
#include "algorithm"
using namespace std;
int dp[10050],in[10050];
int main(){
in[0]=-1;
int n,ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&in[i]);
for(int j=0;j<i;j++){
if(in[i]>in[j]){
dp[i]=max(dp[i],dp[j]+1);
}
ans=max(ans,dp[i]);
}
}
printf("%d\n",ans);
}
O - Treats for the Cows(区间DP)
给一个n,然后给一个由n个数组成的序列,每次只能从首或尾取值,值为取得 次数*原值 ,问如何取值最大。
用递归的思路做,从取完开始还原,比较到当前状态是补上左值更优还是右值更优。
转移方程:
dp[x][y]=max(dp[x+1][y]+in[x]*[n-y+x],dp[x][y - 1] + in[y] * (n - y + x));
代码:
#include "stdio.h"
#include "algorithm"
using namespace std;
int dp[2005][2005];
int in[2005];
int n;
int getdp(int x,int y){
if(x==y) return dp[x][x]=n*in[x];
if(dp[x][y])return dp[x][y];
return dp[x][y]=max(getdp(x+1,y)+in[x]*(n-y+x),getdp(x,y - 1) + in[y] * (n - y + x));
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&in[i]);
printf("%d\n",getdp(1,n));
}
p
Q - Phalanx
线性dp,不过是把一维换成二维
从左下角到右上角开始判断能否成立,时间还是挺宽裕的
代码:
#include <bits/stdc++.h>
using namespace std;
char in[3000][3000];
int dp[3000][3000];
void main(){
int n;
while(scanf("%d",&n),n){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dp[i][j]=1;
for(int i=1;i<=n;i++)
scanf("%s",in[i]+1);
int ans=1;
for(int i=n-1;i>=1;i--){
for(int j=2;j<=n;j++){
int len=dp[i+1][j-1];
for(int k=1;k<=len;k++){
if(i+k>n||j-k<1||in[i+k][j]!=in[i][j-k]) {
break;
}
else {
dp[i][j]++;
ans=max(dp[i][j],ans);
}
}
}
}
printf("%d\n",ans);
}
}
R - Milking Time
给一个n(没用),m,r,然后给m行,每行有一个时间段和权重,选择的时间段不能重叠,且间隔至少为r,问怎么选择可以使权重和最大?
简单线性dp。。。排个序套套公式就ok
代码:
#include "stdio.h"
#include "algorithm"
#include "vector"
#include<cstring>
using namespace std;
#define MAXN 100000
typedef pair<pair<int,int>,int> pii;
vector<pair<pair<int,int>,int> > in;
bool cmp(pii a,pii b){
if(a.first.second!=b.first.second)
return a.first.second<b.first.second;
return a.first.first<b.first.first;
}
int dp[MAXN];
void solve(){
int n,m,r,ans=0;
while(~scanf("%d%d%d",&n,&m,&r)){
ans=0;
memset(dp,0,sizeof(dp));
in.clear();
for(int i=1;i<=m;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
in.push_back({{a,b+r},c});
}
in.push_back({{0,0},0});
sort(in.begin(),in.end(),cmp);
for(int i=1;i<=m;i++){
for(int j=0;j<i;j++){
if(in[i].first.first>=in[j].first.second)
dp[i]=max(dp[i],dp[j]+in[i].second);
}
if(in[i].first.second-r<=n)
ans=max(ans,dp[i]);
}
printf("%d\n",ans);
}
}
int main(){
solve();
}
S
好烦啊,不想讲了
代码:
#include “stdio.h”
#include “algorithm”
using namespace std;
#define MAXN 2050
int lsh[MAXN],num[MAXN];
int cnt;
void LSH(int n){
for(int i=1;i<=n;i++)
lsh[i]=num[i];
sort(lsh+1,lsh+1+n);
cnt=unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=n;i++)
num[i]=lower_bound(lsh+1,lsh+cnt+1,num[i])-lsh;
}
long long dp[MAXN][MAXN];
int main(){
//freopen(“std1.in”,“r”,stdin);
// freopen(“std1.out”,“w”,stdout);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&num[i]);
LSH(n);
//dp[i][j]=min(dp[i-1][k])+ num[i]-j
for(int i=1;i<=cnt;i++)
dp[0][i]=0x3f3f3f3f3f3f3f3f;
dp[0][1]=0;
long long out1=0x3f3f3f3f3f3f3f3f;
for(int i=1;i<=n;i++){
for(int j=1;j<=cnt;j++){
if(j!=1)
dp[i][j]=min(dp[i-1][j],dp[i][j-1]-abs(lsh[num[i]]-lsh[j-1]))+abs(lsh[num[i]]-lsh[j]);
else
dp[i][j]=dp[i-1][1]+abs(lsh[num[i]]-lsh[j]);
if(i==n)
out1=min(out1,dp[i][j]);
}
}
long long out2=0x3f3f3f3f3f3f3f3f;
dp[0][1]=0x3f3f3f3f3f3f3f3f;
dp[0][cnt]=0;
for(int i=1;i<=n;i++){
for(int j=cnt;j>=1;j--){
if(j!=cnt)
dp[i][j]=min(dp[i-1][j],dp[i][j+1]-abs(lsh[num[i]]-lsh[j+1]))+abs(lsh[num[i]]-lsh[j]);
else
dp[i][j]=dp[i-1][cnt]+abs(lsh[num[i]]-lsh[j]);
if(i==n)
out2=min(out2,dp[i][j]);
}
}
printf("%lld\n",min(out1,out2));
}