P1107 雷涛的小猫
一只猫从树上向下吃柿子,它可以向下移动1的高度,还可以跳到任何一棵树上,但它的高度会下降delta。输出猫到达地面的时候能吃到的最多的柿子。
开始我用的三维dp,第i棵树,j的高度,还需要1层循环记录猫随机跳到其他树上的情况。但这数据量使我毫无疑问的超时了…看到题解中大佬们都优化成二维了…
#include <bits/stdc++.h>
using namespace std;
int a[2001][2001],s[2001][2001];
int ans[2001];
int main()
{
int i,j,k,delta,h,n,m,u;
cin>>n>>h>>delta;
for(i=1;i<=n;i++)
{
cin>>m;
for(j=1;j<=m;j++)
{
cin>>u;
a[i][u]++; // 记录下不同树不同高度位置柿子的数量
}
}
for(j=1;j<=h;j++)
{
for(i=1;i<=n;i++)
{
if(j>=delta) s[i][j]=max(s[i][j-1],ans[j-delta])+a[i][j];
// 当高度允许猫在任意树中跳的时候,有向下移动和跳到另一颗树两种情况
else s[i][j]=s[i][j-1]+a[i][j]; //高度只能允许猫向下移动
ans[j]=max(ans[j],s[i][j]); // 求出最大柿子的情况
}
}
cout<<ans[h]<<endl;
}
p1006 传纸条
和p1004方格取数几乎一样,都可以转化为两个人在左上角走到右下角所能取得数的最大值。用四维dp就可以了,不怕麻烦也可以用递推+记忆化搜索(以前的博客里有)。
https://blog.csdn.net/m0_55995088/article/details/120931355?spm=1001.2014.3001.5501
#include <bits/stdc++.h>
using namespace std;
int f[51][51][51][51];
int main()
{ int a[51][51],m,n;
int i,j,l,k,z;
cin>>m>>n;
for(i=1;i<=m;i++)
{
for(j=1;j<=n;j++)
{
cin>>z;
a[i][j]=z;
}
}
for(i=1;i<=m;i++)
{
for(j=1;j<=n;j++)
{
for(l=1;l<=m;l++)
{
for(k=1;k<=n;k++)
{
f[i][j][l][k]=max(max(f[i-1][j][l-1][k],f[i-1][j][l][k-1]),max(f[i][j-1][l-1][k],f[i][j-1][l][k-1]))+a[i][j]+a[l][k];
if(i==l&&j==k) f[i][j][l][k]=f[i][j][l][k]-a[i][j]; //两个人不能走同一个点,所以这里要减去相同的情况
}
}
}
}
cout<<f[m][n][m][n]<<endl;
return 0;
}
p1021邮票面值设计
看题解大部分都用的dp背包+dfs,好不容易看懂了,感觉自己也写不出来,先放着吧…
p1064金明的预算方案
给孩子n块钱,孩子可以买一些物品,这些物品分为主件和附件。想买附件必须要买主件,买主件可以不买附件。每件物品有价钱和优先度,输出孩子用这些钱可以买到的价钱和优先度相乘的最大值。
开始还以为是01背包裸题,得了50分才明白我直接把主件和附件合成一个物品了,和题意不符。
正解:因为附件最多只有两个,用01也不难。但是要有四种情况:只买附件、附件和主件全买、买主件和1号附件、买主件和2号附件。
#include <bits/stdc++.h>
using namespace std;
int dp[32001];
int v[61][3],w[61][3];
int main()
{
int n,m,i,j,x,y,z;
cin>>n>>m;
for(i=1;i<=m;i++)
{
cin>>x>>y>>z;
if(z==0) {
v[i][0]=x*y; // 主件
w[i][0]=x;
}
else{
if(v[z][1]==0) { v[z][1]=x*y; // 1号附件
w[z][1]=x;}
else {
v[z][2]=x*y; // 2号附件
w[z][2]=x;
}
}
}
for(i=1;i<=m;i++)
{
for(j=n;j>=w[i][0];j--)
{
dp[j]=max(dp[j],dp[j-w[i][0]]+v[i][0]);
if(j>=w[i][0]+w[i][1]+w[i][2]) dp[j]=max(dp[j],dp[j-w[i][0]-w[i][1]-w[i][2]]+v[i][0]+v[i][1]+v[i][2]);
if(j>=w[i][0]+w[i][2]) dp[j]=max(dp[j],dp[j-w[i][0]-w[i][2]]+v[i][0]+v[i][2]);
if(j>=w[i][0]+w[i][1]) dp[j]=max(dp[j],dp[j-w[i][0]-w[i][1]]+v[i][0]+v[i][1]);
// 4种情况,4个转移方程。但是不能合成一个,因为要先判断当前的钱数够不够同时买主件和附件。
}
}
cout<<dp[n]<<endl;
}
P2376 Allowance G
妈妈有些硬币,大面值的硬币必然被小面值硬币整除。妈妈每天给孩子一些钱,输出这些硬币最多可以给孩子多少天。
是经典的给硬币题,看着很像完全背包,但是用贪心十分好想。既然是给钱,当然是浪费钱最少能撑比较多的天数,问题就是怎样给,这题难点全在代码上。
#include <bits/stdc++.h>
using namespace std;
struct point
{
int v;
int total;
}a[21];
bool cmp(point a,point b)
{
return a.v>b.v;
}
int main()
{
int n,i,j,m,ans=0,c,k=0,re;
cin>>n>>c;
for(i=0;i<n;i++)
{
cin>>a[i].v>>a[i].total;
}
sort(a,a+n,cmp);
for(i=0;i<n;i++)
{
if(a[i].v>=c) { ans=ans+a[i].total;k++;} //硬币不能掰开,这些大的即使浪费了也没办法。
}
while(1)
{ re=c;
int t=0;
for(i=k;i<n;i++){
if(re<=0) break;
while(re>=a[i].v&&a[i].total>0&&re>0)
{
re=re-a[i].v;
a[i].total=a[i].total-1;
// 由大到小给,每当要浪费的时候就用稍微小的硬币试着给,直到给到循环到头要开始浪费为止
}
}
if(re>0) //只能浪费了,但是浪费也要浪费最少的,用最小面额的硬币糊弄他
{ for(i=n-1;i>=k;i--){
if(re<a[i].v&&a[i].total>0)
{
re=re-a[i].v;
a[i].total--;
break;
}
}
}
if(re>0) break; //浪费也浪费不了,妈妈已经负担不起了
ans++;
}
cout<<ans<<endl;
return 0;
}
P6877 長いだけのネクタイ
奇怪程度:新领带的数值减旧领带的数值,如果小于0就变为0,这是奇怪感。整场聚会的奇怪程度为这些员工中奇怪感的最大值。
n个员工有旧领带,有n+1条新领带,老板从第一个挑到最后一个,输出这些情况每种情况最小的奇怪状态。这我一看就知道把两组数据排序,然后将其相减,但如果直接暴力求的话是二重循环,就必然超时,真让人头疼。
是我没用过的思想:前后缀,虽然每次老板拿走一个不同的领带,但是如果我们提前排好序。这个领带之前的奇怪感不会因为被拿走的这个领带而改变,之后的也是如此。那么就可以提前求出每个位置之前的最大奇怪感和每个位置之后的最大奇怪感,之后每次老板拿走一个领带,直接比较前缀和后缀的最大奇怪感就可以。这就像打表一样,而且它把二重循环优化成了一重循环。
#include <bits/stdc++.h>
using namespace std;
struct point{
int v;
int p;
}a[200002];
bool cmp(point a,point b)
{
return a.v<b.v;
}
int b[200002],l[200002],r[200002],k[200002];
int main()
{
int n,i,j;
cin>>n;
for(i=1;i<=n+1;i++)
{
cin>>a[i].v;
a[i].p=i;
}
for(i=1;i<=n;i++)
{
cin>>b[i];
}
sort(a+1,a+n+2,cmp);
sort(b+1,b+n+1);
for(i=1;i<=n;i++)
{
l[i]=max(l[i-1],max(a[i].v-b[i],0)); // 前缀
}
for(i=n+1;i>1;i--)
{
r[i]=max(r[i+1],max(a[i].v-b[i-1],0)); // 后缀
}
for(i=1;i<=n+1;i++)
{
k[a[i].p]=i;
}
for(i=1;i<=n+1;i++)
{
cout<<max(l[k[i]-1],r[k[i]+1])<<" "; //求前缀和后缀的最大值就行了
}
}
C - Minimum Extraction
给一个数组,可以随机删除一个数,但剩余的数字也会跟着减这个值,求出这个数组中最小值的最大情况。
蠢了,一个简单的贪心也没做出来。贪的不够彻底,贪了一半就开始暴力,结果一直超时,感觉和上个题很像。
#include <iostream>
#include <algorithm>
using namespace std;
int a[200001];
int main()
{ int n,t,sum=0;
cin>>t;
while(t--)
{
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
sort(a,a+n); // 提前排好序,数组中的最小值就很容易看出来了
sum=a[0];
for(int i=1;i<n;i++)
{
sum=max(a[i]-a[i-1],sum); // 直接减就可以了,别像我一样想多了,就算每个数都跟着减,最小值仍是最前面的那个。
}
cout<<sum<<endl;
}
return 0;
}