没错没错就是dp练习赛、dp练习赛。dp专练啊。dp啊dp
T1.接苹果
https://www.luogu.org/problemnew/show/P2690
【问题描述】
奶牛喜欢吃苹果。约翰有两棵苹果树,有 N 只苹果会从树上陆续落下。如果掉苹果的时候,贝西在那棵树下,她就能接住苹果。贝西一开始在第一棵树下。在苹果掉落之前,她有足够的时间来回走动,但她很懒,最多只愿意移动 K 次。请计算一下她最多可以接住几只苹果。
【输入】
• 第一行:两个整数 N 和 K,1 ≤ N ≤ 1000; 1 ≤ K ≤ 30
• 第 i + 1 行有一个整数 Ti,表示第 i 只苹果从哪棵树上掉落,1 表示从第一棵树,2 表示从第二棵树
【输出】
单个整数:表示能接住的最大苹果数量
【题解】
其实这是我在考试中间写出来的第一个满分的dp。好开心好开心开心
虽然这是一道很水的dp……
设:f[i][j][k]表示当前是i时刻,已经移动了j次,在第k-1棵数下。 因为只有两棵树,也就是k=0时在第1棵树下,k=1时在第二棵树下。
#include<bits/stdc++.h>
using namespace std;
int n,k;
int a[2][2010];//a[i][j]表示第i-1棵树在j时刻掉落的苹果数
int f[2010][33][2];
//f[i][j][0]表示当前时刻为i,已经移动了j次,在第一棵树下
//f[i][j][1]表示当前时刻为i,已经移动了j次,在第二棵树下
int main()
{
freopen("bcatch.in","r",stdin);
freopen("bcatch.out","w",stdout);
memset(a,0,sizeof(a));
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
a[x-1][i]=1;
}
memset(f,0,sizeof(f));
f[1][0][0]=a[0][1];
f[1][1][1]=a[1][1];
for (int i=2;i<=n;i++)
for (int j=0;j<=k;j++)
{
if (j!=0)//防止j-1越界
{
f[i][j][0]=max(f[i-1][j][0],f[i-1][j-1][1])+a[0][i];
f[i][j][1]=max(f[i-1][j-1][0],f[i-1][j][1])+a[1][i];
}
else
{
f[i][j][0]=f[i-1][j][0]+a[0][i];
f[i][j][1]=f[i-1][j][1]+a[1][i];
}
}
int ans=-999999999;
for (int i=1;i<=k;i++)
{
ans=max(ans,max(f[n][i][0],f[n][i][1]));
}
cout<<ans<<endl;
}
这样就AC了。但是其实f数组的第三位是不需要的,因为根据移动次数就可以得知当前在哪棵树下。
T2.奶牛飞盘队
【题目描述】
https://www.luogu.org/problemnew/show/P2946
【题解】
第i只牛有两种可能性,要还是不要,所以,这个我们可以往0/1背包上考虑。
因为我们要求的是F的整数倍的方案数,我们只考虑这种可能性显然无法满足最优子结构,按照我们常见的套路,我们显然要把 当前组成的累加和 对 F mod 的值进行存储。
#include<bits/stdc++.h>
using namespace std;
int n,x;
long long k=100000000;
int a[2010];
int f[2010][1010];
int main()
{
// freopen("fristeam.in","r",stdin);
// freopen("fristeam.out","w",stdout);
scanf("%d%d",&n,&x);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
f[i][a[i]]=1;
}
f[0][0]=1;
for (int i=1;i<=n;i++)
for (int j=0;j<=x;j++)
f[i][j]=(f[i-1][j]+f[i-1][((j+a[i])%x)])%k;
printf("%d",f[n][x]);
return 0;
}
T3.股票市场
【题目描述】
https://www.luogu.org/problemnew/show/P2938
【题解】
在第3天购买某种股票然后在第5天卖,可以看成第3天购买,第4天卖出,第4天再购买,第5天再卖出。
我们只关心低价购买,高价卖出,而股票的获利于你持股的时间没有关系,每天的买卖也没有限制,因此我们可以依次考虑每相邻的两天我们如何购买前一天的股票然后在后一天全部卖出。
这样我们得到一个贪心算法,依次考虑每相邻两天,前一天买入后一天全部卖出所能得到的最大收益,而这正是个简单的背包问题:股票就是物品,花费就是前一天的价格,收益就是后一天的价格,背包就是资金。
#include<bits/stdc++.h>
using namespace std;
int n,m,d;
int a[111][111],f[1000010];
long long ans=0;
int main()
{
freopen("stock.in","r",stdin);
freopen("stock.out","w",stdout);
scanf("%d%d%d",&n,&d,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=d;j++)
scanf("%d",&a[i][j]);
for (int k=2;k<=d;k++)
{
memset(f,0,sizeof(f));
int maxx=0;
for (int i=1;i<=n;i++)
for (int j=a[i][k-1];j<=m;j++)//完全背包
{
f[j]=max(f[j],f[j-a[i][k-1]]+a[i][k]-a[i][k-1]);
//第一种情况是不买,第二种就是买:要价格减去买入所花的钱再加上今天和昨天的价格差
maxx=max(f[j],maxx);//一天最大值
}
m+=maxx;//增加收益
}
printf("%d",m);
return 0;
}
T4.248
https://www.luogu.org/problemnew/show/P3146
【题解】
这一题类似于石子合并,应该是一个区间dp。把两个代码放在一起应该就是惊人的相似
石子合并
#include<bits/stdc++.h>
using namespace std;
int n;
int a[333],b[333],c[333][333],f[333][333];
int main()
{
cin>>n;
b[0]=0;
for (int i=1;i<=n;i++)
{
cin>>a[i];
b[i]=b[i-1]+a[i];
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
c[i][j]=b[j]-b[i-1];
for (int len=1;len<=n;len++)
for (int i=1;i<=n;i++)
{
int j=len+i-1;
if (j>n) break;
for (int k=i;k<j;k++)
if (f[i][j]>f[i][k]+f[k+1][j]+c[i][j]||f[i][j]==0)
f[i][j]=f[i][k]+f[k+1][j]+c[i][j];
}
cout<<f[1][n]<<endl;
return 0;
}
248
#include<bits/stdc++.h>
using namespace std;
int n;
int f[255][255];
int maxx=-999999999;
int main()
{
freopen("248.in","r",stdin);
freopen("248.out","w",stdout);
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>f[i][i];
maxx=max(maxx,f[i][i]);
}
for (int len=1;len<=n;len++)
for (int l=1;l+len-1<=n;l++)
{
int r=l+len-1;
for (int i=l;i<=r;i++)
{
if (f[l][i]==f[i+1][r])
f[l][r]=max(f[l][r],f[l][i]+1);
}
maxx=max(maxx,f[l][r]);
}
cout<<maxx<<endl;
return 0;
}
区间之间要合并得到最大的值,但是合并之前有个条件,这两个要合并的区间的值要一样。
这次考试呢,我最开心的就是A了一道dp题。但是剩下的题有点难。所以几乎坐了大半个上午。有些题看上去真的一点思路都没有(比如第三题)。还有我到最后十五分钟的时候才发现第四题和第五题的区别就只是数据范围……